From 76903719ca2f467ad560195633187f436d74c3e7 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 22 Dec 2023 22:49:24 -0500 Subject: [PATCH 01/76] 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 09df6ddbc8ef..41b0f07bf8ac 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 ebfebb73b187..a378055b5109 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 6ad1026cc744..a8492fac20c1 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 79c62e396d47..c8530a87ddb5 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 9bbeb3dd69e8..adcd08278446 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 4646dc6d32b2..7c28af3abb00 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 a963c832c381..e1b31337ffac 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 5849835ea285..32675e55405c 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 5a1e46c24149..edc5aba12378 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; }; From 14805f96cecf86c2f0de727b67261c2e7a176b55 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 22 Dec 2023 22:52:01 -0500 Subject: [PATCH 02/76] fix: lint --- packages/svelte/src/compiler/types/template.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 7c28af3abb00..0b60b1ae7af8 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -148,7 +148,7 @@ export interface DebugTag extends BaseNode { export interface RenderTag extends BaseNode { type: 'RenderTag'; expression: Identifier; - argument: (Expression | SpreadElement)[]; + arguments: (Expression | SpreadElement)[]; } type Tag = ExpressionTag | HtmlTag | ConstTag | DebugTag | RenderTag; From 3756ce5a4fdc66dc67964828b5283d13ed0b96fe Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 22 Dec 2023 22:55:20 -0500 Subject: [PATCH 03/76] fix: Forgot to save --- packages/svelte/src/internal/client/render.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index e1b31337ffac..4aeb067f985d 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2846,13 +2846,13 @@ export function sanitize_slots(props) { * @param {(() => any)[]} args * @returns {void} */ -export function snippet_effect(get_snippet, node, args) { +export function snippet_effect(get_snippet, node, ...args) { const block = create_snippet_block(); render_effect(() => { // Only rerender when the snippet function itself changes, // not when an eagerly-read prop inside the snippet function changes const snippet = get_snippet(); - untrack(() => snippet(node, args)); + untrack(() => snippet(node, ...args)); return () => { if (block.d !== null) { remove(block.d); From 243ba9c0336e49e4f212770dd0220d83dc219515 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 22 Dec 2023 23:29:33 -0500 Subject: [PATCH 04/76] feat: it works boiiii --- .../compiler/phases/3-transform/client/visitors/template.js | 2 +- .../compiler/phases/3-transform/server/transform-server.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 a8492fac20c1..bd07a0e8ae02 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 @@ -2450,7 +2450,7 @@ export const template_visitors = { node.context.elements.forEach((element, i) => { if (!element) return; - const id = element.type === 'Identifier' ? element : b.id(`$$context${i}`); + const id = element.type === 'Identifier' ? element : b.id(`$$arg${i}`); args.push(id); if (element.type === 'Identifier') { 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 c8530a87ddb5..584e6e379f3d 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 @@ -1437,9 +1437,9 @@ const template_visitors = { // TODO hoist where possible /** @type {import('estree').Pattern[]} */ const args = [b.id('$$payload')]; - if (node.context) { - args.push(node.context); - } + node.context.elements.forEach((arg) => { + if (arg) args.push(arg); + }); context.state.init.push( b.function_declaration( From cb4daeac677c7cbf0d70d75ea427cc4e6f563026 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 22 Dec 2023 23:34:48 -0500 Subject: [PATCH 05/76] look, ok, it did work, i just needed to update the snapshots --- .../samples/snippets/output.json | 73 ++++++++++--------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/packages/svelte/tests/parser-modern/samples/snippets/output.json b/packages/svelte/tests/parser-modern/samples/snippets/output.json index 7d7e41425970..968d5564c9ec 100644 --- a/packages/svelte/tests/parser-modern/samples/snippets/output.json +++ b/packages/svelte/tests/parser-modern/samples/snippets/output.json @@ -25,25 +25,30 @@ "name": "foo" }, "context": { - "type": "Identifier", - "name": "msg", - "start": 43, - "end": 54, - "typeAnnotation": { - "type": "TSStringKeyword", - "start": 48, - "end": 54, - "loc": { - "start": { - "line": 1, - "column": 48 - }, - "end": { - "line": 1, - "column": 54 + "type": "ArrayPattern", + "elements": [ + { + "type": "Identifier", + "name": "msg", + "start": 43, + "end": 54, + "typeAnnotation": { + "type": "TSStringKeyword", + "start": 48, + "end": 54, + "loc": { + "start": { + "line": 1, + "column": 48 + }, + "end": { + "line": 1, + "column": 54 + } + } } } - } + ] }, "body": { "type": "Fragment", @@ -127,22 +132,24 @@ }, "name": "foo" }, - "argument": { - "type": "Identifier", - "start": 96, - "end": 99, - "loc": { - "start": { - "line": 7, - "column": 13 + "arguments": [ + { + "type": "Identifier", + "start": 96, + "end": 99, + "loc": { + "start": { + "line": 7, + "column": 13 + }, + "end": { + "line": 7, + "column": 16 + } }, - "end": { - "line": 7, - "column": 16 - } - }, - "name": "msg" - } + "name": "msg" + } + ] } ], "transparent": false @@ -171,4 +178,4 @@ "sourceType": "module" } } -} +} \ No newline at end of file From 8382eb0a2420d1b5b717d8422499d679ff9695c9 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 22 Dec 2023 23:43:54 -0500 Subject: [PATCH 06/76] bruh --- .../svelte/tests/parser-modern/samples/snippets/output.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/tests/parser-modern/samples/snippets/output.json b/packages/svelte/tests/parser-modern/samples/snippets/output.json index 968d5564c9ec..c43a6e422360 100644 --- a/packages/svelte/tests/parser-modern/samples/snippets/output.json +++ b/packages/svelte/tests/parser-modern/samples/snippets/output.json @@ -178,4 +178,4 @@ "sourceType": "module" } } -} \ No newline at end of file +} From 5bab2db39e5a0c5c6f46c8d97dd8395db4e6ce97 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 22 Dec 2023 23:54:50 -0500 Subject: [PATCH 07/76] changeset --- .changeset/curvy-cups-cough.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/curvy-cups-cough.md diff --git a/.changeset/curvy-cups-cough.md b/.changeset/curvy-cups-cough.md new file mode 100644 index 000000000000..470e8b846a0f --- /dev/null +++ b/.changeset/curvy-cups-cough.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: Variadic snippets From 5beaf36d35921eec2fe1425c45a9430f6778fa13 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 24 Dec 2023 01:08:45 -0500 Subject: [PATCH 08/76] feat: ok I think the client snippet block finally works --- .../src/compiler/phases/1-parse/state/tag.js | 62 +++++++++++-------- .../compiler/phases/1-parse/utils/bracket.js | 25 ++++++++ .../3-transform/client/visitors/template.js | 37 ++++++++--- 3 files changed, 89 insertions(+), 35 deletions(-) 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 a378055b5109..cf80f0e1bddd 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -3,7 +3,9 @@ import read_expression from '../read/expression.js'; import { error } from '../../../errors.js'; import { create_fragment } from '../utils/create.js'; import { walk } from 'zimmerframe'; -import { parse } from '../acorn.js'; +import { parse, parse_expression_at } from '../acorn.js'; +import { find_matching_bracket } from '../utils/bracket.js'; +import full_char_code_at from '../utils/full_char_code_at.js'; const regex_whitespace_with_closing_curly_brace = /^\s*}/; @@ -263,29 +265,40 @@ function open(parser) { return; } - if (parser.eat('snippet')) { - parser.require_whitespace(); - - const name_start = parser.index; - const name = parser.read_identifier(); - const name_end = parser.index; - - parser.eat('(', true); - - parser.allow_whitespace(); + if (parser.match('snippet')) { + const snippet_declaraion_end = find_matching_bracket( + parser, + full_char_code_at(parser.template, start) + ); + // we'll eat this later, so save it + const raw_snippet_declaration = parser.template.slice(start, snippet_declaraion_end); + const start_subtractions = '{#snippet '; + const end_subtractions = '}'; + + // now the snippet declaration is just a function declaration (`function snipName() {}`) + const snippet_declaration = `function ${raw_snippet_declaration.slice( + start_subtractions.length, + raw_snippet_declaration.length - end_subtractions.length + )} {}`; + + // we offset the index by one because `function` takes the same space as `#snippet` + const snippet_expression = parse_expression_at( + `${parser.template.slice(0, parser.index - 1)}${snippet_declaration}`, + parser.ts, + parser.index - 1 + ); - const elements = []; - while (!parser.match(')')) { - elements.push(read_context(parser)); - parser.eat(','); - parser.allow_whitespace(); + // TODO: handle error + if (snippet_expression.type !== 'FunctionExpression') { + throw new Error(); } - parser.allow_whitespace(); - parser.eat(')', true); + // TODO: handle error + if (!snippet_expression.id) { + throw new Error(); + } - parser.allow_whitespace(); - parser.eat('}', true); + parser.eat(raw_snippet_declaration); const block = parser.append( /** @type {Omit} */ @@ -293,15 +306,10 @@ function open(parser) { type: 'SnippetBlock', start, end: -1, - expression: { - type: 'Identifier', - start: name_start, - end: name_end, - name - }, + expression: snippet_expression.id, context: { type: 'ArrayPattern', - elements + elements: snippet_expression.params }, body: create_fragment() }) diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js index aa1e072e7348..5406b001af6b 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js @@ -1,3 +1,5 @@ +import full_char_code_at from './full_char_code_at.js'; + const SQUARE_BRACKET_OPEN = '['.charCodeAt(0); const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0); const CURLY_BRACKET_OPEN = '{'.charCodeAt(0); @@ -33,3 +35,26 @@ export function get_bracket_close(open) { return CURLY_BRACKET_CLOSE; } } + +/** + * The function almost known as bracket_fight + * @param {import('..').Parser} parser + * @param {number} open + * @returns {number} + */ +export function find_matching_bracket(parser, open) { + const close = get_bracket_close(open); + let brackets = 1; + let i = parser.index; + while (brackets > 0 && i < parser.template.length) { + const code = full_char_code_at(parser.template, i); + if (code === open) { + brackets++; + } else if (code === close) { + brackets--; + } + i++; + } + // TODO: If not found + return i; +} 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 bd07a0e8ae02..f3a2cbb2afac 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 @@ -2440,6 +2440,7 @@ export const template_visitors = { }, SnippetBlock(node, context) { // TODO hoist where possible + /** @type {import('estree').Pattern[]} */ const args = [b.id('$$anchor')]; /** @type {import('estree').BlockStatement} */ @@ -2448,18 +2449,38 @@ export const template_visitors = { /** @type {import('estree').Statement[]} */ const declarations = []; - node.context.elements.forEach((element, i) => { - if (!element) return; - const id = element.type === 'Identifier' ? element : b.id(`$$arg${i}`); - args.push(id); + node.context.elements.forEach((argument, i) => { + if (!argument) return; + // If the argument is itself an identifier (`foo`) or is a simple rest identifier (`...foo`) + // we don't have to do anything fancy -- just push it onto the args array and make sure its + // binding expression is a call of itself. + /** @type {import('estree').Identifier | undefined} */ + let identifier; + /** @type {import('estree').Identifier | import('estree').RestElement | string} */ + let arg; + if (argument.type === 'Identifier') { + identifier = argument; + arg = argument; + } else if (argument.type === 'RestElement' && argument.argument.type === 'Identifier') { + identifier = argument.argument; + arg = argument; + } else if (argument.type === 'RestElement') { + arg = b.rest(b.id(`$$arg${i}`)); + } else { + arg = b.id(`$$arg${i}`); + } + args.push(arg); - if (element.type === 'Identifier') { + if (identifier) { const binding = /** @type {import('#compiler').Binding} */ ( - context.state.scope.get(id.name) + context.state.scope.get(identifier.name) ); - binding.expression = b.call(id); + binding.expression = b.call(identifier); } else { - const paths = extract_paths(element); + // If, on the other hand, it isn't an identifier, it's a destructuring expression, which could be + // a rest destructure (eg. `...[foo, bar, { baz }, ...rest]`, which, absurdly, is all valid syntax). + // In this case, we need to follow the destructuring expression to figure out what variables are being extracted. + const paths = extract_paths(argument.type === 'RestElement' ? argument.argument : argument); for (const path of paths) { const name = /** @type {import('estree').Identifier} */ (path.node).name; From 33a39bf85ebe436d618eeb60ada666d34d0f840f Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 24 Dec 2023 01:50:10 -0500 Subject: [PATCH 09/76] feat: current tests pass; I'm sure I'm missing stuff for new things --- .../src/compiler/phases/1-parse/state/tag.js | 3 ++- .../3-transform/client/visitors/template.js | 14 +++++++++++--- .../3-transform/server/transform-server.js | 17 ++++++----------- packages/svelte/src/internal/client/utils.js | 15 +++++++++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) 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 cf80f0e1bddd..a71ddd5fa364 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -298,7 +298,8 @@ function open(parser) { throw new Error(); } - parser.eat(raw_snippet_declaration); + // slice the `{#` off the beginning since it's already been eaten + parser.eat(raw_snippet_declaration.slice(2), true); const block = parser.append( /** @type {Omit} */ 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 f3a2cbb2afac..06f7beb35cd5 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,17 @@ export const template_visitors = { /** @type {import('estree').Expression[]} */ const args = [context.state.node]; - node.arguments.forEach((arg) => - args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(arg)))) - ); + node.arguments.forEach((arg) => { + if (arg.type === 'SpreadElement') { + // this is a spread operation, meaning we need to thunkify all of its members + args.push( + /** @type {import('estree').Expression} */ ( + context.visit(b.spread(b.call('$.shallow_thunk', arg.argument))) + ) + ); + } + args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(arg)))); + }); let snippet_function = /** @type {import('estree').Expression} */ ( context.visit(node.expression) 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 584e6e379f3d..9250647d37e7 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,18 +1127,13 @@ const template_visitors = { const snippet_function = state.options.dev ? b.call('$.validate_snippet', node.expression) : node.expression; + + const snippet_args = node.arguments.map((arg) => { + return /** @type {import('estree').Expression} */ (context.visit(arg)); + }); + 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)) - ) - ) - ) - ) + t_statement(b.stmt(b.call(snippet_function, b.id('$$payload'), ...snippet_args))) ); state.template.push(t_expression(anchor_id)); diff --git a/packages/svelte/src/internal/client/utils.js b/packages/svelte/src/internal/client/utils.js index 627d2b34fb95..79e5b3d79de9 100644 --- a/packages/svelte/src/internal/client/utils.js +++ b/packages/svelte/src/internal/client/utils.js @@ -16,3 +16,18 @@ export var get_descriptors = Object.getOwnPropertyDescriptors; export function is_function(thing) { return typeof thing === 'function'; } + +/** + * TODO: Do the types matter on this? If so, can we improve them so that + * `shallow_thunk(['one', 2])` returns a tuple type instead of a union? + * @template {unknown} T + * @param {Iterable} iterable + * @returns {(() => T)[]} + */ +function shallow_thunk(iterable) { + const thunks = []; + for (const item of iterable) { + thunks.push(() => item); + } + return thunks; +} From c1bb2397dd9796d690d9259f4759e6b1e4e0b517 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 24 Dec 2023 01:53:00 -0500 Subject: [PATCH 10/76] fix: snapshot --- .../3-transform/client/visitors/template.js | 3 +- .../samples/snippets/output.json | 53 +++++++++++++++---- 2 files changed, 46 insertions(+), 10 deletions(-) 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 06f7beb35cd5..505ef37544b8 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 @@ -1757,7 +1757,8 @@ export const template_visitors = { const args = [context.state.node]; node.arguments.forEach((arg) => { if (arg.type === 'SpreadElement') { - // this is a spread operation, meaning we need to thunkify all of its members + // this is a spread operation, meaning we need to thunkify all of its members, which we can't + // do until runtime args.push( /** @type {import('estree').Expression} */ ( context.visit(b.spread(b.call('$.shallow_thunk', arg.argument))) diff --git a/packages/svelte/tests/parser-modern/samples/snippets/output.json b/packages/svelte/tests/parser-modern/samples/snippets/output.json index c43a6e422360..2bf5cff6ca0e 100644 --- a/packages/svelte/tests/parser-modern/samples/snippets/output.json +++ b/packages/svelte/tests/parser-modern/samples/snippets/output.json @@ -22,6 +22,16 @@ "type": "Identifier", "start": 39, "end": 42, + "loc": { + "start": { + "line": 3, + "column": 10 + }, + "end": { + "line": 3, + "column": 13 + } + }, "name": "foo" }, "context": { @@ -29,21 +39,46 @@ "elements": [ { "type": "Identifier", - "name": "msg", "start": 43, - "end": 54, + "end": 25, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 25 + } + }, + "name": "msg", "typeAnnotation": { - "type": "TSStringKeyword", - "start": 48, + "type": "TSTypeAnnotation", + "start": 46, "end": 54, "loc": { "start": { - "line": 1, - "column": 48 + "line": 3, + "column": 17 }, "end": { - "line": 1, - "column": 54 + "line": 3, + "column": 25 + } + }, + "typeAnnotation": { + "type": "TSStringKeyword", + "start": 48, + "end": 54, + "loc": { + "start": { + "line": 3, + "column": 19 + }, + "end": { + "line": 3, + "column": 25 + } } } } @@ -178,4 +213,4 @@ "sourceType": "module" } } -} +} \ No newline at end of file From 3a726a9c39630f698bc132ac4e99f2e3212a6075 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 25 Dec 2023 22:35:03 -0500 Subject: [PATCH 11/76] feat: I think non-destructured rest should work now? --- .../3-transform/client/visitors/template.js | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) 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 505ef37544b8..bdf8a23f697e 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 @@ -2460,9 +2460,7 @@ export const template_visitors = { node.context.elements.forEach((argument, i) => { if (!argument) return; - // If the argument is itself an identifier (`foo`) or is a simple rest identifier (`...foo`) - // we don't have to do anything fancy -- just push it onto the args array and make sure its - // binding expression is a call of itself. + const arg_alias = `$$arg${i}`; /** @type {import('estree').Identifier | undefined} */ let identifier; /** @type {import('estree').Identifier | import('estree').RestElement | string} */ @@ -2470,9 +2468,6 @@ export const template_visitors = { if (argument.type === 'Identifier') { identifier = argument; arg = argument; - } else if (argument.type === 'RestElement' && argument.argument.type === 'Identifier') { - identifier = argument.argument; - arg = argument; } else if (argument.type === 'RestElement') { arg = b.rest(b.id(`$$arg${i}`)); } else { @@ -2485,36 +2480,46 @@ export const template_visitors = { context.state.scope.get(identifier.name) ); binding.expression = b.call(identifier); - } else { - // If, on the other hand, it isn't an identifier, it's a destructuring expression, which could be - // a rest destructure (eg. `...[foo, bar, { baz }, ...rest]`, which, absurdly, is all valid syntax). - // In this case, we need to follow the destructuring expression to figure out what variables are being extracted. - const paths = extract_paths(argument.type === 'RestElement' ? argument.argument : argument); - - for (const path of paths) { - const name = /** @type {import('estree').Identifier} */ (path.node).name; - const binding = /** @type {import('#compiler').Binding} */ ( - context.state.scope.get(name) - ); - declarations.push( - b.let( - path.node, - b.thunk( - /** @type {import('estree').Expression} */ ( - context.visit(path.expression?.(b.call(`$$arg${i}`))) - ) + return; + } + + if (argument.type === 'RestElement' && argument.argument.type === 'Identifier') { + // this is a "simple" rest argument (not destructured), so we just need to thunkify it + const binding = /** @type {import('#compiler').Binding} */ ( + context.state.scope.get(argument.argument.name) + ); + binding.expression = b.call(argument.argument.name); + // TODO: where does `context.visit` go here? does it matter? i don't understand what it's for :thinkies: + declarations.push(b.let(argument.argument.name, b.thunk(b.id(`$$arg${i}`)))); + return; + } + + // If, on the other hand, it's a destructuring expression, which could be + // a rest destructure (eg. `...[foo, bar, { baz }, ...rest]`, which, absurdly, is all valid syntax), + // we need to follow the destructuring expression to figure out what variables are being extracted. + const paths = extract_paths(argument.type === 'RestElement' ? argument.argument : argument); + + for (const path of paths) { + const name = /** @type {import('estree').Identifier} */ (path.node).name; + const binding = /** @type {import('#compiler').Binding} */ (context.state.scope.get(name)); + declarations.push( + b.let( + path.node, + b.thunk( + /** @type {import('estree').Expression} */ ( + context.visit(path.expression?.(b.call(`$$arg${i}`))) ) ) - ); - - // we need to eagerly evaluate the expression in order to hit any - // 'Cannot access x before initialization' errors - if (context.state.options.dev) { - declarations.push(b.stmt(b.call(name))); - } + ) + ); - binding.expression = b.call(name); + // we need to eagerly evaluate the expression in order to hit any + // 'Cannot access x before initialization' errors + if (context.state.options.dev) { + declarations.push(b.stmt(b.call(name))); } + + binding.expression = b.call(name); } }); From 8555681bcb95d711a3a1ba6d94bb2f81264bd3ae Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 25 Dec 2023 23:53:09 -0500 Subject: [PATCH 12/76] chore: duplicated computation --- .../phases/3-transform/client/visitors/template.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 bdf8a23f697e..dcf53155a233 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 @@ -1486,13 +1486,7 @@ function process_children(nodes, parent, { visit, state }) { ); const assignment = serialize_template_literal(sequence, visit, state)[1]; const init = b.stmt(b.assignment('=', b.member(text_id, b.id('nodeValue')), assignment)); - const singular = b.stmt( - b.call( - '$.text_effect', - text_id, - b.thunk(serialize_template_literal(sequence, visit, state)[1]) - ) - ); + const singular = b.stmt(b.call('$.text_effect', text_id, b.thunk(assignment))); if (contains_call_expression && !within_bound_contenteditable) { state.update_effects.push(singular); From 6d364aaaded8adfbab80f0c993107297f9a6f845 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 26 Dec 2023 00:06:36 -0500 Subject: [PATCH 13/76] feat: Tests (passing and failing --- .../_config.js | 21 +++++++++++++++++++ .../main.svelte | 14 +++++++++++++ .../snippet-argument-multiple/_config.js | 21 +++++++++++++++++++ .../snippet-argument-multiple/main.svelte | 14 +++++++++++++ .../_config.js | 21 +++++++++++++++++++ .../main.svelte | 15 +++++++++++++ .../samples/snippet-argument-rest/_config.js | 21 +++++++++++++++++++ .../samples/snippet-argument-rest/main.svelte | 15 +++++++++++++ 8 files changed, 142 insertions(+) create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-destructured-multiple/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-destructured-multiple/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-multiple/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-multiple/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-destructured-multiple/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-argument-destructured-multiple/_config.js new file mode 100644 index 000000000000..e756ff5cc237 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-argument-destructured-multiple/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +export default test({ + html: ` +

clicks: 0, doubled: 0

+ + `, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + + await btn?.click(); + assert.htmlEqual( + target.innerHTML, + ` +

clicks: 1, doubled: 2

+ + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-destructured-multiple/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-argument-destructured-multiple/main.svelte new file mode 100644 index 000000000000..34583ae1ccc4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-argument-destructured-multiple/main.svelte @@ -0,0 +1,14 @@ + + +{#snippet foo({ count }, { doubled })} +

clicks: {count}, doubled: {doubled}

+{/snippet} + +{@render foo({ count }, { doubled })} + + diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-multiple/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-argument-multiple/_config.js new file mode 100644 index 000000000000..e756ff5cc237 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-argument-multiple/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +export default test({ + html: ` +

clicks: 0, doubled: 0

+ + `, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + + await btn?.click(); + assert.htmlEqual( + target.innerHTML, + ` +

clicks: 1, doubled: 2

+ + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-multiple/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-argument-multiple/main.svelte new file mode 100644 index 000000000000..6de6a8564773 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-argument-multiple/main.svelte @@ -0,0 +1,14 @@ + + +{#snippet foo(n: number, doubled: number)} +

clicks: {n}, doubled: {doubled}

+{/snippet} + +{@render foo(count, doubled)} + + diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/_config.js new file mode 100644 index 000000000000..6c0f9cbb31ee --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +export default test({ + html: ` +

clicks: 0, doubled: 0, tripled: 0

+ + `, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + + await btn?.click(); + assert.htmlEqual( + target.innerHTML, + ` +

clicks: 1, doubled: 2, tripled: 3

+ + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/main.svelte new file mode 100644 index 000000000000..263f399a0518 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/main.svelte @@ -0,0 +1,15 @@ + + +{#snippet foo(n: number, ...[doubled, { tripled }]: number[])} +

clicks: {n}, doubled: {doubled}, tripled: {tripled}

+{/snippet} + +{@render foo(count, doubled, {tripled})} + + diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/_config.js new file mode 100644 index 000000000000..6c0f9cbb31ee --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +export default test({ + html: ` +

clicks: 0, doubled: 0, tripled: 0

+ + `, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + + await btn?.click(); + assert.htmlEqual( + target.innerHTML, + ` +

clicks: 1, doubled: 2, tripled: 3

+ + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/main.svelte new file mode 100644 index 000000000000..05ac668a2e12 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/main.svelte @@ -0,0 +1,15 @@ + + +{#snippet foo(n: number, ...rest: number[])} +

clicks: {n}, doubled: {rest[0]}, tripled: {rest[1]}

+{/snippet} + +{@render foo(count, doubled, tripled)} + + From 3da243f5ab1d68edc150bdf4fae92f6e4c7e7b1e Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 26 Dec 2023 16:52:06 -0500 Subject: [PATCH 14/76] feat: it's... alive? --- .../3-transform/client/visitors/template.js | 29 ++++++++++---- .../svelte/src/internal/client/runtime.js | 34 +++++++++++++++++ packages/svelte/src/internal/client/utils.js | 15 -------- packages/svelte/src/internal/index.js | 4 +- .../samples/snippet-spread-args/_config.js | 21 ++++++++++ .../samples/snippet-spread-args/main.svelte | 38 +++++++++++++++++++ 6 files changed, 117 insertions(+), 24 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-spread-args/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-spread-args/main.svelte 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 dcf53155a233..5f80a1d6ca36 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 @@ -1758,6 +1758,7 @@ export const template_visitors = { context.visit(b.spread(b.call('$.shallow_thunk', arg.argument))) ) ); + return; } args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(arg)))); }); @@ -2454,7 +2455,7 @@ export const template_visitors = { node.context.elements.forEach((argument, i) => { if (!argument) return; - const arg_alias = `$$arg${i}`; + let arg_alias = `$$arg${i}`; /** @type {import('estree').Identifier | undefined} */ let identifier; /** @type {import('estree').Identifier | import('estree').RestElement | string} */ @@ -2478,16 +2479,24 @@ export const template_visitors = { } if (argument.type === 'RestElement' && argument.argument.type === 'Identifier') { - // this is a "simple" rest argument (not destructured), so we just need to thunkify it - const binding = /** @type {import('#compiler').Binding} */ ( - context.state.scope.get(argument.argument.name) - ); - binding.expression = b.call(argument.argument.name); + // this is a "simple" rest argument (not destructured), so we just need to proxy it + // const binding = /** @type {import('#compiler').Binding} */ ( + // context.state.scope.get(argument.argument.name) + // ); + // binding.expression = b.call(argument.argument.name); // TODO: where does `context.visit` go here? does it matter? i don't understand what it's for :thinkies: - declarations.push(b.let(argument.argument.name, b.thunk(b.id(`$$arg${i}`)))); + declarations.push( + b.let(argument.argument.name, b.call('$.proxy_rest_array', b.id(arg_alias))) + ); return; } + if (argument.type === 'RestElement') { + const new_arg_alias = `$$proxied_arg${i}`; + declarations.push(b.let(new_arg_alias, b.call('$.proxy_rest_array', b.id(arg_alias)))); + arg_alias = new_arg_alias; + } + // If, on the other hand, it's a destructuring expression, which could be // a rest destructure (eg. `...[foo, bar, { baz }, ...rest]`, which, absurdly, is all valid syntax), // we need to follow the destructuring expression to figure out what variables are being extracted. @@ -2501,7 +2510,11 @@ export const template_visitors = { path.node, b.thunk( /** @type {import('estree').Expression} */ ( - context.visit(path.expression?.(b.call(`$$arg${i}`))) + context.visit( + path.expression?.( + argument.type === 'RestElement' ? b.id(arg_alias) : b.call(arg_alias) + ) + ) ) ) ) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 6fb4cc927a02..4fb93502c84d 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1899,3 +1899,37 @@ if (DEV) { throw_rune_error('$inspect'); throw_rune_error('$props'); } + +/** + * @template {Iterable} T + * @param {T} iterable + * @returns {{ [P in keyof T]: () => T[P] }} + */ +export function shallow_thunk(iterable) { + const thunks = []; + for (const item of iterable) { + thunks.push(() => item); + } + // @ts-expect-error -- for obvious reasons + return thunks; +} + +/** + * @template {unknown[]} T + * @param {T} items + * @returns {T} + */ +export function proxy_rest_array(items) { + return new Proxy(items, { + get(target, property) { + // @ts-expect-error -- It thinks arrays can't have properties that aren't numeric + if (typeof property === 'symbol') return target[property]; + if (!isNaN(parseInt(property))) { + // @ts-expect-error -- It thinks arrays can't have properties that aren't numeric + return target[property](); + } + // @ts-expect-error -- It thinks arrays can't have properties that aren't numeric + return target[property]; + } + }); +} diff --git a/packages/svelte/src/internal/client/utils.js b/packages/svelte/src/internal/client/utils.js index 79e5b3d79de9..627d2b34fb95 100644 --- a/packages/svelte/src/internal/client/utils.js +++ b/packages/svelte/src/internal/client/utils.js @@ -16,18 +16,3 @@ export var get_descriptors = Object.getOwnPropertyDescriptors; export function is_function(thing) { return typeof thing === 'function'; } - -/** - * TODO: Do the types matter on this? If so, can we improve them so that - * `shallow_thunk(['one', 2])` returns a tuple type instead of a union? - * @template {unknown} T - * @param {Iterable} iterable - * @returns {(() => T)[]} - */ -function shallow_thunk(iterable) { - const thunks = []; - for (const item of iterable) { - thunks.push(() => item); - } - return thunks; -} diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 048105d0f978..56fd9031a188 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -36,7 +36,9 @@ export { effect_active, user_root_effect, inspect, - unwrap + unwrap, + proxy_rest_array, + shallow_thunk } from './client/runtime.js'; export * from './client/each.js'; diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/_config.js new file mode 100644 index 000000000000..82418110df0b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +export default test({ + html: ` +

clicks: 0, doubled: 0, tripled: 0, quadrupled: 0, something else: 0

+ + `, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + + await btn?.click(); + assert.htmlEqual( + target.innerHTML, + ` +

clicks: 1, doubled: 2, tripled: 3, quadrupled: 4, something else: 5

+ + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/main.svelte new file mode 100644 index 000000000000..b2f2ae13e2dc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/main.svelte @@ -0,0 +1,38 @@ + + +{#snippet foo(n: number, ...[doubled, { tripled }, ...rest]: number[])} +

clicks: {n.value}, doubled: {doubled.value}, tripled: {tripled.value}, quadrupled: {rest[0].value}, something else: {rest[1].value}

+{/snippet} + +{@render foo(...[count, doubled, {tripled}, quadrupled, whatever_comes_after_that])} + + From 49893ecb44ef5ecae68eb337960665843c809374 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 26 Dec 2023 17:05:08 -0500 Subject: [PATCH 15/76] chore: Clean up my messes --- .../3-transform/client/visitors/template.js | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) 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 5f80a1d6ca36..424d313aeb4c 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 @@ -2455,46 +2455,33 @@ export const template_visitors = { node.context.elements.forEach((argument, i) => { if (!argument) return; - let arg_alias = `$$arg${i}`; - /** @type {import('estree').Identifier | undefined} */ - let identifier; - /** @type {import('estree').Identifier | import('estree').RestElement | string} */ - let arg; - if (argument.type === 'Identifier') { - identifier = argument; - arg = argument; - } else if (argument.type === 'RestElement') { - arg = b.rest(b.id(`$$arg${i}`)); - } else { - arg = b.id(`$$arg${i}`); - } - args.push(arg); - if (identifier) { + if (argument.type === 'Identifier') { + args.push(argument); const binding = /** @type {import('#compiler').Binding} */ ( - context.state.scope.get(identifier.name) + context.state.scope.get(argument.name) ); - binding.expression = b.call(identifier); + binding.expression = b.call(argument); return; } - if (argument.type === 'RestElement' && argument.argument.type === 'Identifier') { - // this is a "simple" rest argument (not destructured), so we just need to proxy it - // const binding = /** @type {import('#compiler').Binding} */ ( - // context.state.scope.get(argument.argument.name) - // ); - // binding.expression = b.call(argument.argument.name); - // TODO: where does `context.visit` go here? does it matter? i don't understand what it's for :thinkies: - declarations.push( - b.let(argument.argument.name, b.call('$.proxy_rest_array', b.id(arg_alias))) - ); - return; - } + let arg_alias = `$$arg${i}`; if (argument.type === 'RestElement') { + args.push(b.rest(b.id(arg_alias))); + + if (argument.argument.type === 'Identifier') { + declarations.push( + b.let(argument.argument.name, b.call('$.proxy_rest_array', b.id(arg_alias))) + ); + return; + } + const new_arg_alias = `$$proxied_arg${i}`; declarations.push(b.let(new_arg_alias, b.call('$.proxy_rest_array', b.id(arg_alias)))); arg_alias = new_arg_alias; + } else { + args.push(b.id(arg_alias)); } // If, on the other hand, it's a destructuring expression, which could be From 398bb676a03dbcb8b20d2110fcefb48562ebe3a8 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 26 Dec 2023 17:10:33 -0500 Subject: [PATCH 16/76] chore: devtime stuff --- .../phases/3-transform/client/visitors/template.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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 424d313aeb4c..49fa8ed47e8c 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 @@ -2474,19 +2474,27 @@ export const template_visitors = { declarations.push( b.let(argument.argument.name, b.call('$.proxy_rest_array', b.id(arg_alias))) ); + + // we need to eagerly evaluate the expression in order to hit any + // 'Cannot access x before initialization' errors + if (context.state.options.dev) { + declarations.push(b.stmt(b.call(argument.argument.name))); + } return; } const new_arg_alias = `$$proxied_arg${i}`; declarations.push(b.let(new_arg_alias, b.call('$.proxy_rest_array', b.id(arg_alias)))); + // we need to eagerly evaluate the expression in order to hit any + // 'Cannot access x before initialization' errors + if (context.state.options.dev) { + declarations.push(b.stmt(b.call(new_arg_alias))); + } arg_alias = new_arg_alias; } else { args.push(b.id(arg_alias)); } - // If, on the other hand, it's a destructuring expression, which could be - // a rest destructure (eg. `...[foo, bar, { baz }, ...rest]`, which, absurdly, is all valid syntax), - // we need to follow the destructuring expression to figure out what variables are being extracted. const paths = extract_paths(argument.type === 'RestElement' ? argument.argument : argument); for (const path of paths) { From bb63c85c9c56f6bd0af65718f11fbdf41399cd02 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 26 Dec 2023 17:14:00 -0500 Subject: [PATCH 17/76] fix: fmt --- .../svelte/tests/parser-modern/samples/snippets/output.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/tests/parser-modern/samples/snippets/output.json b/packages/svelte/tests/parser-modern/samples/snippets/output.json index 2bf5cff6ca0e..ecf3dee8968a 100644 --- a/packages/svelte/tests/parser-modern/samples/snippets/output.json +++ b/packages/svelte/tests/parser-modern/samples/snippets/output.json @@ -213,4 +213,4 @@ "sourceType": "module" } } -} \ No newline at end of file +} From c0d62c41c86e1fde6bf9db902d3ae226c8a1bc82 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 26 Dec 2023 23:42:03 -0500 Subject: [PATCH 18/76] chore: see if this fixes repl --- .../phases/3-transform/client/visitors/template.js | 11 ----------- 1 file changed, 11 deletions(-) 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 49fa8ed47e8c..cdefefeda296 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 @@ -2474,22 +2474,11 @@ export const template_visitors = { declarations.push( b.let(argument.argument.name, b.call('$.proxy_rest_array', b.id(arg_alias))) ); - - // we need to eagerly evaluate the expression in order to hit any - // 'Cannot access x before initialization' errors - if (context.state.options.dev) { - declarations.push(b.stmt(b.call(argument.argument.name))); - } return; } const new_arg_alias = `$$proxied_arg${i}`; declarations.push(b.let(new_arg_alias, b.call('$.proxy_rest_array', b.id(arg_alias)))); - // we need to eagerly evaluate the expression in order to hit any - // 'Cannot access x before initialization' errors - if (context.state.options.dev) { - declarations.push(b.stmt(b.call(new_arg_alias))); - } arg_alias = new_arg_alias; } else { args.push(b.id(arg_alias)); From 42e7bf47fb72f432fe1c5cbb3ae6728fd4f23a0c Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 26 Dec 2023 23:53:46 -0500 Subject: [PATCH 19/76] chore: make naming more offensive --- .../src/compiler/phases/3-transform/client/visitors/template.js | 2 +- packages/svelte/src/internal/client/runtime.js | 2 +- packages/svelte/src/internal/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 cdefefeda296..220a4cd8090e 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,7 +1755,7 @@ export const template_visitors = { // do until runtime args.push( /** @type {import('estree').Expression} */ ( - context.visit(b.spread(b.call('$.shallow_thunk', arg.argument))) + context.visit(b.spread(b.call('$.thunkspread', arg.argument))) ) ); return; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 4fb93502c84d..1d89b1dd68a7 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1905,7 +1905,7 @@ if (DEV) { * @param {T} iterable * @returns {{ [P in keyof T]: () => T[P] }} */ -export function shallow_thunk(iterable) { +export function thunkspread(iterable) { const thunks = []; for (const item of iterable) { thunks.push(() => item); diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 56fd9031a188..0e2a0d12ff87 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -38,7 +38,7 @@ export { inspect, unwrap, proxy_rest_array, - shallow_thunk + thunkspread } from './client/runtime.js'; export * from './client/each.js'; From 83218b102f380d9f067e4a7bc7ab3d4642a32f8e Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 28 Dec 2023 14:53:12 -0500 Subject: [PATCH 20/76] fix: Don't throw on missing keys, return undefined as it usually would --- packages/svelte/src/internal/client/runtime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 01bc2f8531a0..3659a315ae0c 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1926,7 +1926,7 @@ export function proxy_rest_array(items) { if (typeof property === 'symbol') return target[property]; if (!isNaN(parseInt(property))) { // @ts-expect-error -- It thinks arrays can't have properties that aren't numeric - return target[property](); + return target[property]?.(); } // @ts-expect-error -- It thinks arrays can't have properties that aren't numeric return target[property]; From 381fd7b8131552cf0e6870cd9e422e9da975a6ee Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 2 Jan 2024 14:46:11 -0500 Subject: [PATCH 21/76] Update packages/svelte/src/compiler/phases/1-parse/state/tag.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- packages/svelte/src/compiler/phases/1-parse/state/tag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a71ddd5fa364..1d6580e9e5c9 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -290,7 +290,7 @@ function open(parser) { // TODO: handle error if (snippet_expression.type !== 'FunctionExpression') { - throw new Error(); + error(snippet_expression, 'TODO', 'expected a function expression'); } // TODO: handle error From 36601effe93f3276cf93ffbab8439381d25dc73e Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 2 Jan 2024 14:46:23 -0500 Subject: [PATCH 22/76] Update packages/svelte/src/compiler/phases/1-parse/state/tag.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- packages/svelte/src/compiler/phases/1-parse/state/tag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1d6580e9e5c9..8e11f0fae0e3 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -295,7 +295,7 @@ function open(parser) { // TODO: handle error if (!snippet_expression.id) { - throw new Error(); + error(snippet_expression, 'TODO', 'expected a snippet name'); } // slice the `{#` off the beginning since it's already been eaten From cfee4356b3f808965fb9740c244db7f860f6b216 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 5 Jan 2024 15:47:35 -0700 Subject: [PATCH 23/76] fix: Hopefully default param values now work --- .../phases/3-transform/client/visitors/template.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 66f9a54ebfca..0fa47dedf728 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 @@ -2486,12 +2486,15 @@ export const template_visitors = { node.context.elements.forEach((argument, i) => { if (!argument) return; + console.log(argument); + const call = argument.type === 'AssignmentPattern' ? b.maybe_call : b.call; + if (argument.type === 'Identifier') { args.push(argument); const binding = /** @type {import('#compiler').Binding} */ ( context.state.scope.get(argument.name) ); - binding.expression = b.call(argument); + binding.expression = call(argument); return; } @@ -2526,7 +2529,7 @@ export const template_visitors = { /** @type {import('estree').Expression} */ ( context.visit( path.expression?.( - argument.type === 'RestElement' ? b.id(arg_alias) : b.call(arg_alias) + argument.type === 'RestElement' ? b.id(arg_alias) : call(arg_alias) ) ) ) From e6ddbba9efdbe609dd932bc420be53ab18d7adeb Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 5 Jan 2024 15:48:00 -0700 Subject: [PATCH 24/76] dumb --- .../src/compiler/phases/3-transform/client/visitors/template.js | 1 - 1 file changed, 1 deletion(-) 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 0fa47dedf728..8654428e5b4f 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 @@ -2486,7 +2486,6 @@ export const template_visitors = { node.context.elements.forEach((argument, i) => { if (!argument) return; - console.log(argument); const call = argument.type === 'AssignmentPattern' ? b.maybe_call : b.call; if (argument.type === 'Identifier') { From 99ed291434beb93ebeefbb26ec262a9a890a689a Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 5 Jan 2024 16:02:34 -0700 Subject: [PATCH 25/76] types --- packages/svelte/types/index.d.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 7baeab418975..818664182633 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -196,8 +196,11 @@ declare module 'svelte' { * ``` * 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"'; }; } @@ -321,7 +324,7 @@ declare module 'svelte' { new (options: ComponentConstructorOptions | undefined; + children?: Snippet<[]> | undefined; })>): SvelteComponent; }, options: { target: Node; @@ -344,7 +347,7 @@ declare module 'svelte' { new (options: ComponentConstructorOptions | undefined; + children?: Snippet<[]> | undefined; })>): SvelteComponent; }, options: { target: Node; @@ -484,7 +487,7 @@ declare module 'svelte/animate' { } declare module 'svelte/compiler' { - import type { AssignmentExpression, ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, ArrayExpression, MemberExpression, ObjectExpression, Pattern, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, FunctionExpression, Node, Program } from 'estree'; + import type { AssignmentExpression, ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, ArrayExpression, MemberExpression, ObjectExpression, Pattern, ArrowFunctionExpression, ArrayPattern, VariableDeclaration, VariableDeclarator, FunctionExpression, Node, Program, SpreadElement } from 'estree'; import type { Location } from 'locate-character'; import type { SourceMap } from 'magic-string'; import type { Context } from 'zimmerframe'; @@ -1178,7 +1181,7 @@ declare module 'svelte/compiler' { interface RenderTag extends BaseNode { type: 'RenderTag'; expression: Identifier; - argument: null | Expression; + arguments: (Expression | SpreadElement)[]; } type Tag = ExpressionTag | HtmlTag | ConstTag | DebugTag | RenderTag; @@ -1445,7 +1448,7 @@ declare module 'svelte/compiler' { interface SnippetBlock extends BaseNode { type: 'SnippetBlock'; expression: Identifier; - context: null | Pattern; + context: ArrayPattern; body: Fragment; } @@ -1722,7 +1725,7 @@ declare module 'svelte/legacy' { } ? {} : Slots extends { default: any; } ? { - children?: Snippet | undefined; + children?: Snippet<[]> | undefined; } : {})>): SvelteComponent; } & Exports; // This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are). @@ -1848,8 +1851,11 @@ declare module 'svelte/legacy' { * ``` * You can only call a snippet through the `{@render ...}` tag. */ - interface Snippet { - (arg: T): typeof SnippetReturn & { + interface Snippet { + ( + this: void, + ...args: T + ): typeof SnippetReturn & { _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; }; } From bc917fe9a4dffa2a6c9d25a65e8d50ca1539227a Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 5 Jan 2024 16:20:48 -0700 Subject: [PATCH 26/76] feat: Test it --- .../snippet-complicated-defaults/_config.js | 31 +++++++++++++++++++ .../snippet-complicated-defaults/main.svelte | 26 ++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-complicated-defaults/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-complicated-defaults/main.svelte diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-complicated-defaults/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-complicated-defaults/_config.js new file mode 100644 index 000000000000..4a403b328111 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-complicated-defaults/_config.js @@ -0,0 +1,31 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + /** @type {HTMLButtonElement | null} */ + const increment = target.querySelector('#increment'); + /** @type {HTMLButtonElement | null} */ + const change_ref = target.querySelector('#change-ref'); + /** @type {HTMLParagraphElement | null} */ + const count = target.querySelector('#count'); + /** @type {HTMLParagraphElement | null} */ + const fallback_count = target.querySelector('#fallback-count'); + + assert.htmlEqual(count?.innerHTML ?? '', 'Count: 0'); + assert.htmlEqual(fallback_count?.innerHTML ?? '', 'Fallback count: 0'); + + await increment?.click(); + assert.htmlEqual(count?.innerHTML ?? '', 'Count: 1'); + assert.htmlEqual(fallback_count?.innerHTML ?? '', 'Fallback count: 0'); + + await change_ref?.click(); + await increment?.click(); + assert.htmlEqual(count?.innerHTML ?? '', 'Count: 1'); + assert.htmlEqual(fallback_count?.innerHTML ?? '', 'Fallback count: 1'); + + await change_ref?.click(); + await increment?.click(); + assert.htmlEqual(count?.innerHTML ?? '', 'Count: 2'); + assert.htmlEqual(fallback_count?.innerHTML ?? '', 'Fallback count: 1'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-complicated-defaults/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-complicated-defaults/main.svelte new file mode 100644 index 000000000000..cd31067575fe --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-complicated-defaults/main.svelte @@ -0,0 +1,26 @@ + + +{#snippet counter(c = count)} +

Count: {count.value}

+

Fallback count: {fallback_count.value}

+ + +{/snippet} + +{@render counter(toggle_state ? fallback_count : undefined)} From 106514514d1b00c8a691da998889f1501f2cc26e Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 5 Jan 2024 16:37:42 -0700 Subject: [PATCH 27/76] fix: Turns out javascript parameters are optional --- .../compiler/phases/3-transform/client/visitors/template.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 8654428e5b4f..a526fd1c2e50 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 @@ -2486,14 +2486,12 @@ export const template_visitors = { node.context.elements.forEach((argument, i) => { if (!argument) return; - const call = argument.type === 'AssignmentPattern' ? b.maybe_call : b.call; - if (argument.type === 'Identifier') { args.push(argument); const binding = /** @type {import('#compiler').Binding} */ ( context.state.scope.get(argument.name) ); - binding.expression = call(argument); + binding.expression = b.maybe_call(argument); return; } @@ -2528,7 +2526,7 @@ export const template_visitors = { /** @type {import('estree').Expression} */ ( context.visit( path.expression?.( - argument.type === 'RestElement' ? b.id(arg_alias) : call(arg_alias) + argument.type === 'RestElement' ? b.id(arg_alias) : b.maybe_call(arg_alias) ) ) ) From d7dac4eac2181b4598c4565326bc4366d5e06f11 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 5 Jan 2024 17:12:45 -0700 Subject: [PATCH 28/76] feat: The Final Solution --- .../3-transform/client/visitors/template.js | 9 ++++-- .../svelte/src/internal/client/runtime.js | 12 ++++++++ packages/svelte/src/internal/index.js | 1 + .../snippet-optional-arguments/_config.js | 15 ++++++++++ .../snippet-optional-arguments/main.svelte | 28 +++++++++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/main.svelte 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 a526fd1c2e50..d544e9d3992d 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 @@ -2491,7 +2491,10 @@ export const template_visitors = { const binding = /** @type {import('#compiler').Binding} */ ( context.state.scope.get(argument.name) ); - binding.expression = b.maybe_call(argument); + // we can't use `b.maybe_call` because it can result in invalid javascript if + // this expression appears on the left side of an assignment somewhere. For example: + // `$.maybe_call(myArg).value = 1` is valid JavaScript, but `$.myArg?.().value = 1` is not + binding.expression = b.call('$.maybe_call', argument); return; } @@ -2526,7 +2529,9 @@ export const template_visitors = { /** @type {import('estree').Expression} */ ( context.visit( path.expression?.( - argument.type === 'RestElement' ? b.id(arg_alias) : b.maybe_call(arg_alias) + argument.type === 'RestElement' + ? b.id(arg_alias) + : b.call('$.maybe_call', b.id(arg_alias)) ) ) ) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index a95253c73185..4bf8d7806f03 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1993,6 +1993,18 @@ export function proxy_rest_array(items) { }); } +/** + * @template {Function | undefined} T + * @param {T} fn + * @returns {ReturnType | undefined} + */ +export function maybe_call(fn) { + if (fn === undefined) { + return undefined; + } + return fn(); +} + /** * Expects a value that was wrapped with `freeze` and makes it frozen. * @template {import('./proxy/proxy.js').StateObject} T diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index eaeb746b0af6..a5f789ee7168 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -39,6 +39,7 @@ export { unwrap, proxy_rest_array, thunkspread, + maybe_call, freeze } from './client/runtime.js'; diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/_config.js new file mode 100644 index 000000000000..203cb1c43267 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/_config.js @@ -0,0 +1,15 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const count = target.querySelector('button'); + const fallback = target.querySelector('p'); + + assert.htmlEqual(count?.innerHTML ?? '', '0'); + assert.htmlEqual(fallback?.innerHTML ?? '', 'fallback'); + + await count?.click(); + assert.htmlEqual(count?.innerHTML ?? '', '1'); + assert.htmlEqual(fallback?.innerHTML ?? '', 'fallback'); + } +}); 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 new file mode 100644 index 000000000000..03d3c29c450d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-optional-arguments/main.svelte @@ -0,0 +1,28 @@ + + +{#snippet counter(c)} + {#if c} + + {:else} +

fallback

+ {/if} +{/snippet} + +{@render counter()} +{@render counter(count)} + From 79536e48751c9ee3f8939531ff651bb5b5b950d5 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 5 Jan 2024 17:28:24 -0700 Subject: [PATCH 29/76] document function --- packages/svelte/src/internal/client/runtime.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 4bf8d7806f03..168f59380b5e 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1974,6 +1974,12 @@ export function thunkspread(iterable) { } /** + * This is meant to proxy the `...rest` parameter to a snippet function. Basically, + * this array will be full of functions that need to be invoked to unwrap their value. + * We have no way of forcing that invocation in all circumstances -- for example, if + * a user passes the rest array to a function that then accesses it via `rest[0]`, we + * would need to transform that into `rest[0]()`. That's effectively what this proxy does. + * * @template {unknown[]} T * @param {T} items * @returns {T} From 1dc662aa4d7639df5e1e809f846dfeda1fc0d746 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 8 Jan 2024 18:07:09 -0700 Subject: [PATCH 30/76] feat: Better bracket matching, unit tests --- .../src/compiler/phases/1-parse/state/tag.js | 14 +- .../compiler/phases/1-parse/utils/bracket.js | 134 ++++++++++++++++-- .../phases/1-parse/utils/bracket.test.ts | 111 +++++++++++++++ vitest.config.js | 1 + 4 files changed, 240 insertions(+), 20 deletions(-) create mode 100644 packages/svelte/src/compiler/phases/1-parse/utils/bracket.test.ts 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 8e11f0fae0e3..0b1a8fe3f21e 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -267,11 +267,17 @@ function open(parser) { if (parser.match('snippet')) { const snippet_declaraion_end = find_matching_bracket( - parser, - full_char_code_at(parser.template, start) + parser.template, + parser.index, + parser.template[start] ); + + if (!snippet_declaraion_end) { + error(parser.current(), 'TODO', 'Expected a closing curly bracket'); + } + // we'll eat this later, so save it - const raw_snippet_declaration = parser.template.slice(start, snippet_declaraion_end); + const raw_snippet_declaration = parser.template.slice(start, snippet_declaraion_end + 1); const start_subtractions = '{#snippet '; const end_subtractions = '}'; @@ -288,12 +294,10 @@ function open(parser) { parser.index - 1 ); - // TODO: handle error if (snippet_expression.type !== 'FunctionExpression') { error(snippet_expression, 'TODO', 'expected a function expression'); } - // TODO: handle error if (!snippet_expression.id) { error(snippet_expression, 'TODO', 'expected a snippet name'); } diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js index 5406b001af6b..57ffbb886e3e 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js @@ -37,24 +37,128 @@ export function get_bracket_close(open) { } /** - * The function almost known as bracket_fight - * @param {import('..').Parser} parser - * @param {number} open + * @param {number} number + * @returns {number} + */ +function infinity_if_negative(number) { + if (number < 0) { + return Infinity; + } + return number; +} + +/** + * @param {string} string The string to search. + * @param {number} search_start_index The index to start searching at. + * @param {"'" | '"' | '`'} string_start_char The character that started this string. * @returns {number} */ -export function find_matching_bracket(parser, open) { - const close = get_bracket_close(open); +function find_string_end(string, search_start_index, string_start_char) { + let string_to_search; + if (string_start_char === '`') { + string_to_search = string; + } else { + // we could slice at the search start index, but this way the index remains valid + string_to_search = string.slice( + 0, + infinity_if_negative(string.indexOf('\n', search_start_index)) + ); + } + + return find_unescaped_char(string_to_search, search_start_index, string_start_char); +} + +/** + * @param {string} string The string to search. + * @param {number} search_start_index The index to start searching at. + * @returns {number} Infinity if not found, else the index of the character. + */ +function find_regex_end(string, search_start_index) { + return find_unescaped_char(string, search_start_index, '/'); +} + +/** + * + * @param {string} string The string to search. + * @param {number} search_start_index The index to begin the search at. + * @param {string} char The character to search for. + * @returns + */ +function find_unescaped_char(string, search_start_index, char) { + let i = search_start_index; + while (true) { + const found_index = string.indexOf(char, i); + if (found_index === -1) { + return Infinity; + } + if (count_leading_backslashes(string, found_index - 1) % 2 === 0) { + return found_index; + } + i = found_index + 1; + } +} + +/** + * @param {string} string + * @param {number} search_start_index + */ +function count_leading_backslashes(string, search_start_index) { + let i = search_start_index; + let count = 0; + while (string[i] === '\\') { + count++; + i--; + } + return count; +} + +/** + * The function almost known as bracket_fight + * @param {string} template + * @param {number} index + * @param {string} open + * @returns {number | undefined} + */ +export function find_matching_bracket(template, index, open) { + const open_code = full_char_code_at(open, 0); + const close_code = get_bracket_close(open_code); let brackets = 1; - let i = parser.index; - while (brackets > 0 && i < parser.template.length) { - const code = full_char_code_at(parser.template, i); - if (code === open) { - brackets++; - } else if (code === close) { - brackets--; + let i = index; + while (brackets > 0 && i < template.length) { + const char = template[i]; + switch (char) { + case "'": + case '"': + case '`': + i = find_string_end(template, i + 1, char) + 1; + continue; + case '/': { + const next_char = template[i + 1]; + if (!next_char) continue; + if (next_char === '/') { + i = infinity_if_negative(template.indexOf('\n', i + 1)) + '\n'.length; + continue; + } + if (next_char === '*') { + i = infinity_if_negative(template.indexOf('*/', i + 1)) + '*/'.length; + continue; + } + i = find_regex_end(template, i + 1) + '/'.length; + continue; + } + default: { + const code = full_char_code_at(template, i); + if (code === open_code) { + brackets++; + } else if (code === close_code) { + brackets--; + } + if (brackets === 0) { + return i; + } + i++; + } } - i++; } - // TODO: If not found - return i; + return undefined; } diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.test.ts b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.test.ts new file mode 100644 index 000000000000..abb1145411e9 --- /dev/null +++ b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, it } from 'vitest'; +import { find_matching_bracket, get_bracket_close } from './bracket'; +import full_char_code_at from './full_char_code_at'; + +describe('find_matching_bracket', () => { + it.each([ + { + name: 'no-gotchas-const', + template: `{@const foo = bar}`, + start: '{', + index: 1, + expected: 17 + }, + { + name: 'no-gotchas-snippet', + template: `{#snippet foo(bar, baz)}`, + start: '{', + index: 1, + expected: 23 + }, + { + name: 'multiple-bracket-pairs-snippet', + template: `{#snippet foo(bar, { baz })}`, + start: '{', + index: 1, + expected: 27 + }, + { + name: 'unbalanced-brackets-snippet', + template: `{#snippet foo(bar, { baz })`, + start: '{', + index: 1, + expected: undefined + }, + { + name: 'singlequote-string-snippet', + template: `{#snippet foo(bar = '}')}`, + start: '{', + index: 1, + expected: 24 + }, + { + name: 'doublequote-string-snippet', + template: `{#snippet foo(bar = "}")}`, + start: '{', + index: 1, + expected: 24 + }, + { + name: 'backquote-string-snippet', + template: '{#snippet foo(bar = `}`)}', + start: '{', + index: 1, + expected: 24 + }, + { + name: 'multiline-backquote-string-snippet', + template: `{#snippet foo(bar = \`} + \`)}`, + start: '{', + index: 1, + expected: 28 + }, + { + name: 'escaped-string-delimiter-snippet', + template: `{#snippet foo(bar = '\\'')}`, + start: '{', + index: 1, + expected: 25 + }, + { + name: 'regex-snippet', + template: `{#snippet foo(bar = /foobar'"/)}`, + start: '{', + index: 1, + expected: 31 + }, + { + name: 'inline-multiline-comment-snippet', + template: `{#snippet foo(bar /*inline multiline comment*/)}`, + start: '{', + index: 1, + expected: 47 + }, + { + name: 'linebreak-multiline-comment-snippet', + template: `{#snippet foo(bar /*inline multiline comment + */) + }`, + start: '{', + index: 1, + expected: 55 + }, + { + name: 'confusing-singleline-comment-snippet', + template: `{#snippet foo(bar) // this comment removes the bracket inside of it } + }`, + start: '{', + index: 1, + expected: 72 + } + ])('finds the matching bracket ($name)', ({ template, index, start, expected }) => { + const matched_bracket_location = find_matching_bracket(template, index, start); + expect(matched_bracket_location).toBe(expected); + if (matched_bracket_location) { + expect(get_bracket_close(full_char_code_at(start, 0))).toBe( + full_char_code_at(template[matched_bracket_location], 0) + ); + } + }); +}); diff --git a/vitest.config.js b/vitest.config.js index 30e3917caa61..2af320902dbf 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -37,6 +37,7 @@ export default defineConfig({ dir: '.', reporters: ['dot'], include: [ + 'packages/svelte/**/*.test.ts', 'packages/svelte/tests/*/test.ts', 'packages/svelte/tests/runtime-browser/test-ssr.ts' ], From 31b78f09d7956fae46d4d36ce7339fb14fd167df Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 8 Jan 2024 18:08:52 -0700 Subject: [PATCH 31/76] feat: exclude test files from publish --- packages/svelte/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/package.json b/packages/svelte/package.json index a1b62c22cf3b..8de3865c92f4 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -9,6 +9,7 @@ "node": ">=18" }, "files": [ + "!src/**/*.test.*", "src", "types", "compiler.cjs", From a7be93c5386fcb0aaaf8f5fefc07cef6c6c58bd2 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 8 Jan 2024 18:20:22 -0700 Subject: [PATCH 32/76] feat: More unit tests --- .../svelte/src/internal/client/runtime.js | 8 ++-- .../src/internal/client/runtime.test.ts | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 packages/svelte/src/internal/client/runtime.test.ts diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 168f59380b5e..1570e11d8616 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1960,16 +1960,16 @@ if (DEV) { } /** - * @template {Iterable} T - * @param {T} iterable - * @returns {{ [P in keyof T]: () => T[P] }} + * @template {unknown} TItems + * @template {Iterable} TIterator + * @param {TIterator} iterable + * @returns {(() => TItems)[]} */ export function thunkspread(iterable) { const thunks = []; for (const item of iterable) { thunks.push(() => item); } - // @ts-expect-error -- for obvious reasons return thunks; } diff --git a/packages/svelte/src/internal/client/runtime.test.ts b/packages/svelte/src/internal/client/runtime.test.ts new file mode 100644 index 000000000000..ecd19102cc18 --- /dev/null +++ b/packages/svelte/src/internal/client/runtime.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; +import { proxy_rest_array, thunkspread } from '..'; + +describe('thunkspread', () => { + it('makes all of its arguments callable', () => { + const items = [1, 2, 'three', 4, { five: 5 }]; + const thunks = thunkspread(items); + expect(thunks.map((thunk) => thunk())).toEqual(items); + }); + + it('works with iterables', () => { + function* items() { + for (const item of [1, 2, 'three', 4, { five: 5 }]) { + yield item; + } + } + const items_iterator = items(); + const thunks = thunkspread(items_iterator); + expect(thunks.map((thunk) => thunk())).toEqual([...items()]); + }); +}); + +describe('proxy_rest_array', () => { + it('calls its items on access', () => { + const items = [() => 1, () => 2, () => 3]; + const proxied_items = proxy_rest_array(items); + expect(proxied_items[1]).toBe(2); + }); + + it('returns undefined for keys with no item', () => { + const items = [() => 1, () => 2, () => 3]; + const proxied_items = proxy_rest_array(items); + expect(proxied_items[4]).toBe(undefined); + }); + + it('works with array methods', () => { + const items = [() => 1, () => 2, () => 3]; + const proxied_items = proxy_rest_array(items); + expect(proxied_items.map((item) => item)).toEqual([1, 2, 3]); + // @ts-expect-error - This is a weird case for sure + expect(proxied_items.find((item) => item === 1)).toBe(1); + }); +}); From eb26b84df6d093927ebf5fdfe14c96bbc67b305c Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Mon, 8 Jan 2024 18:32:43 -0700 Subject: [PATCH 33/76] feat: Use more efficient parsing for @const --- .../src/compiler/phases/1-parse/state/tag.js | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) 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 0b1a8fe3f21e..e5163aad7d77 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -266,14 +266,10 @@ function open(parser) { } if (parser.match('snippet')) { - const snippet_declaraion_end = find_matching_bracket( - parser.template, - parser.index, - parser.template[start] - ); + const snippet_declaraion_end = find_matching_bracket(parser.template, parser.index, '{'); if (!snippet_declaraion_end) { - error(parser.current(), 'TODO', 'Expected a closing curly bracket'); + error(start, 'TODO', 'Expected a closing curly bracket'); } // we'll eat this later, so save it @@ -551,36 +547,24 @@ function special(parser) { return; } - if (parser.eat('const')) { + if (parser.match('const')) { // {@const a = b} - const start_index = parser.index - 5; - parser.require_whitespace(); + const start_index = parser.index; - let end_index = parser.index; - /** @type {import('estree').VariableDeclaration | undefined} */ - let declaration = undefined; + let end_index = find_matching_bracket(parser.template, start_index, '{'); + if (!end_index) { + error(start, 'invalid-const'); + } - // Can't use parse_expression_at here, so we try to parse until we find the correct range const dummy_spaces = parser.template.substring(0, start_index).replace(/[^\n]/g, ' '); - while (true) { - end_index = parser.template.indexOf('}', end_index + 1); - if (end_index === -1) break; - try { - const node = parse( - dummy_spaces + parser.template.substring(start_index, end_index), - parser.ts - ).body[0]; - if (node?.type === 'VariableDeclaration') { - declaration = node; - break; - } - } catch (e) { - continue; - } - } + let declaration = parse( + dummy_spaces + parser.template.substring(start_index, end_index), + parser.ts + ).body[0]; if ( declaration === undefined || + declaration.type !== 'VariableDeclaration' || declaration.declarations.length !== 1 || declaration.declarations[0].init === undefined ) { From 88b06c7c42eb06af71f5315c3f84b13c5353f440 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 11:53:10 -0700 Subject: [PATCH 34/76] Update .changeset/curvy-cups-cough.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/curvy-cups-cough.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/curvy-cups-cough.md b/.changeset/curvy-cups-cough.md index 470e8b846a0f..fef0cf7b4ecc 100644 --- a/.changeset/curvy-cups-cough.md +++ b/.changeset/curvy-cups-cough.md @@ -1,5 +1,5 @@ --- -'svelte': minor +'svelte': patch --- -feat: Variadic snippets +breaking: snippets can now take multiple arguments From 83973edb2b8938f1801cfffe60277ac6529b5dbe Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 11:53:37 -0700 Subject: [PATCH 35/76] Update packages/svelte/package.json Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- packages/svelte/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/package.json b/packages/svelte/package.json index f1f6f6a8104a..746ea1636233 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -9,8 +9,8 @@ "node": ">=18" }, "files": [ - "!src/**/*.test.*", "src", + "!src/**/*.test.*", "types", "compiler.cjs", "*.d.ts", From 55a57b40f6cda4672eb7e4d6babf6a032f1e1e9e Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 11:54:04 -0700 Subject: [PATCH 36/76] Update packages/svelte/src/compiler/phases/1-parse/utils/bracket.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- packages/svelte/src/compiler/phases/1-parse/utils/bracket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js index 57ffbb886e3e..f1d6628d8ca0 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js @@ -113,7 +113,7 @@ function count_leading_backslashes(string, search_start_index) { } /** - * The function almost known as bracket_fight + * Finds the corresponding closing bracket taking into account strings, comments and regex * @param {string} template * @param {number} index * @param {string} open From 6e97647f5c8c33fc8dd009c6d99adb0beccae275 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 11:55:20 -0700 Subject: [PATCH 37/76] fix: changesets --- .changeset/curvy-cups-cough.md | 2 +- .changeset/eighty-rivers-wash.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/eighty-rivers-wash.md diff --git a/.changeset/curvy-cups-cough.md b/.changeset/curvy-cups-cough.md index fef0cf7b4ecc..5185886b0225 100644 --- a/.changeset/curvy-cups-cough.md +++ b/.changeset/curvy-cups-cough.md @@ -2,4 +2,4 @@ 'svelte': patch --- -breaking: snippets can now take multiple arguments +feat: snippets can now take multiple arguments diff --git a/.changeset/eighty-rivers-wash.md b/.changeset/eighty-rivers-wash.md new file mode 100644 index 000000000000..4fa81fc2e65c --- /dev/null +++ b/.changeset/eighty-rivers-wash.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: snippets now support default parameter values for regular and destructured parameters From 330a3c0c5e485d99bb1d1e4c8b758884b8ff1d02 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 12:08:06 -0700 Subject: [PATCH 38/76] chore: additional comments --- .../compiler/phases/1-parse/utils/bracket.js | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js index f1d6628d8ca0..4e02f06de61b 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js @@ -37,21 +37,21 @@ export function get_bracket_close(open) { } /** - * @param {number} number - * @returns {number} + * @param {number} num + * @returns {number} Infinity if {@link num} is negative, else {@link num}. */ -function infinity_if_negative(number) { - if (number < 0) { +function infinity_if_negative(num) { + if (num < 0) { return Infinity; } - return number; + return num; } /** * @param {string} string The string to search. * @param {number} search_start_index The index to start searching at. * @param {"'" | '"' | '`'} string_start_char The character that started this string. - * @returns {number} + * @returns {number} The index of the end of this string expression, or `Infinity` if not found. */ function find_string_end(string, search_start_index, string_start_char) { let string_to_search; @@ -71,7 +71,7 @@ function find_string_end(string, search_start_index, string_start_char) { /** * @param {string} string The string to search. * @param {number} search_start_index The index to start searching at. - * @returns {number} Infinity if not found, else the index of the character. + * @returns {number} The index of the end of this regex expression, or `Infinity` if not found. */ function find_regex_end(string, search_start_index) { return find_unescaped_char(string, search_start_index, '/'); @@ -82,7 +82,7 @@ function find_regex_end(string, search_start_index) { * @param {string} string The string to search. * @param {number} search_start_index The index to begin the search at. * @param {string} char The character to search for. - * @returns + * @returns {number} The index of the first unescaped instance of {@link char}, or `Infinity` if not found. */ function find_unescaped_char(string, search_start_index, char) { let i = search_start_index; @@ -99,8 +99,15 @@ function find_unescaped_char(string, search_start_index, char) { } /** - * @param {string} string - * @param {number} search_start_index + * Count consecutive leading backslashes before {@link search_start_index}. + * + * @example + * ```js + * count_leading_backslashes('\\\\\\foo', 2); // 3 (the backslashes have to be escaped in the string literal, there are three in reality) + * ``` + * + * @param {string} string The string to search. + * @param {number} search_start_index The index to begin the search at. */ function count_leading_backslashes(string, search_start_index) { let i = search_start_index; @@ -113,11 +120,11 @@ function count_leading_backslashes(string, search_start_index) { } /** - * Finds the corresponding closing bracket taking into account strings, comments and regex - * @param {string} template - * @param {number} index - * @param {string} open - * @returns {number | undefined} + * Finds the corresponding closing bracket, ignoring brackets found inside comments, strings, or regex expressions. + * @param {string} template The string to search. + * @param {number} index The index to begin the search at. + * @param {string} open The opening bracket (ex: `'{'` will search for `'}'`). + * @returns {number | undefined} The index of the closing bracket, or undefined if not found. */ export function find_matching_bracket(template, index, open) { const open_code = full_char_code_at(open, 0); From 9a688cc543ff231dbdb93623e06b0e2701c12b88 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 12:11:52 -0700 Subject: [PATCH 39/76] fix: kill foreach --- .../phases/3-transform/client/visitors/template.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 d544e9d3992d..f80caa250594 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 @@ -1775,7 +1775,7 @@ export const template_visitors = { /** @type {import('estree').Expression[]} */ const args = [context.state.node]; - node.arguments.forEach((arg) => { + for (const arg of node.arguments) { if (arg.type === 'SpreadElement') { // this is a spread operation, meaning we need to thunkify all of its members, which we can't // do until runtime @@ -1787,7 +1787,7 @@ export const template_visitors = { return; } args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(arg)))); - }); + } let snippet_function = /** @type {import('estree').Expression} */ ( context.visit(node.expression) @@ -2483,7 +2483,9 @@ export const template_visitors = { /** @type {import('estree').Statement[]} */ const declarations = []; - node.context.elements.forEach((argument, i) => { + for (let i = 0; i < node.context.elements.length; i++) { + const argument = node.context.elements[i]; + if (!argument) return; if (argument.type === 'Identifier') { @@ -2547,7 +2549,7 @@ export const template_visitors = { binding.expression = b.call(name); } - }); + } body = b.block([ ...declarations, From 3b6f5b8e38cc8d8e2b73907c30b1702424a31f7c Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 12:18:52 -0700 Subject: [PATCH 40/76] fix: foreach again --- .../compiler/phases/3-transform/server/transform-server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 621b8d659e41..f5d0cb32fbeb 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 @@ -1428,9 +1428,9 @@ const template_visitors = { // TODO hoist where possible /** @type {import('estree').Pattern[]} */ const args = [b.id('$$payload')]; - node.context.elements.forEach((arg) => { + for (const arg of node.context.elements) { if (arg) args.push(arg); - }); + } context.state.init.push( b.function_declaration( From 4c5ecd28d2f98be0cdb8fa7d81a58b24180dc3e6 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 12:37:52 -0700 Subject: [PATCH 41/76] feat: Docs --- .../routes/docs/content/01-api/03-snippets.md | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md index b227a9a9d0c0..b86081d6f579 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md @@ -58,7 +58,7 @@ Snippets, and _render tags_, are a way to create reusable chunks of markup insid {/each} ``` -A snippet can have at most one parameter. You can destructure it, just like a function argument ([demo](/#H4sIAAAAAAAAE5VTYW-bMBD9KyeiKYlEY4jWfSAk2n5H6QcXDmwVbMs2SzuL_z6DTRqp2rQJ2Ycfd_ced2eXtLxHkxRPLhF0wKRIfiiVpIl9V_PB_MTeoj8bOep6RkpTa67spRKV7dECH2iHBs7wNCOVdcFU1ui6gC2zVpmCEMVrMw4HxaSVhnzLMnLMsm26Ol95Y1kBHr9BDHnHbAHHO6ymynIpfF7LuAncwKgBCj0Xrx_5mMb2jh3f6KB6PNRy2AaXKf1fuY__KPfxj3KlQGikL5aQdpUxm-dTJUryUVdRsvwSqEviX2fIbYzgSvmCt7wbNe4ceMUpRIoUFkkpBBkw7ZfMZXC-BLKSDx3Q3p5djJrA-SR-X4K9DdHT6u-jo-flFlKSO3ThIDcSR6LIKUhGWrN1QGhs16LLbXgbjoe5U1PkozCfzu7uy2WtpfuuUTSo1_9ffPZrJKGLoyuwNxjBv0Q4wmdSR2aFi9jS2Pc-FIrlEKeilcI-GP4LfVtxOM1gyO1XSLp6vtD6tdNyFE0BV8YtngKuaNNw0RWQx_jKDlR33M9E5h-PQhZxfxEt6gIaLdWDYbSR191RvcFXv_LMb7p7obssXZ5Dvt_f9HgzdzZKibOZZ9mXmHkdTTpaefqsd4OIay4_hksd_I0fZMNbjk1SWD3i9Dz9BpdEPu8sBAAA)): +A snippet behaves pretty much like a regular function declaration: It can have multiple parameters, those parameters can be destructured, and they can have default values. You can also use `...rest` params. ([demo](/#H4sIAAAAAAAAE2WO0YrCQAxFfyXGhVoo9L3Wot9hF6xt1IHpTJikggzz78tI2YX1MTecc2_Em7Ek2JwjumEmbPDEjBXqi_MhT7JKWKH4JYw5aWUMhrXrXa-WFCLMJDLcCQ5wMVoIOK8gHu6BBgX1IETw8ssFEhzgi4Nn2ZX73rX1n8rFrTjDTAoPstbv8pjqQ_3hLFPe0XL3piBmLG0grmDatDV3vYv1ak_vrmMgN1FYq4rBmpGKrPr_ufpr8buiTFjh7CdzMzRho2Gh9J1-AFhYxBNCAQAA)): ```svelte {#snippet figure({ src, caption, width, height })} @@ -225,4 +225,40 @@ They continue to work, however, and you can mix and match snippets and slots in ## Typing snippets -Right now, it's not possible to add types for snippets and their parameters. This is something we hope to address before we ship Svelte 5. +You can import the `Snippet` type from `'svelte'`: + +```svelte + +``` + +The `Snippet` type is generic. Here's how you'd type various cases: + +```ts +type SnippetWithNoArgs = Snippet +type SnippetWithOneArg = Snippet<[argOne: number]> +type SnippetWithMultipleArgs = Snippet<[argOne: number, argTwo: string]> +type SnippetWithAnyNumberOfArgs = Snippet +``` + +And here are the snippet declarations matching those cases (note: this example uses TypeScript): + +```svelte +{#snippet withNoArgs()} + +{/snippet} + +{#snippet withOneArg(argOne: number)} + +{/snippet} + +{#snippet withMultipleArgs(argOne: number, argTwo: string)} + +{/snippet} + +{#snippet withAnyNumberOfArgs(...rest: number[])} + +{/snippet} +``` From 82694acadcde533a9c29663ee3fe07519e798dfe Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 13:21:11 -0700 Subject: [PATCH 42/76] Revert "fix: kill foreach" This reverts commit 9a688cc543ff231dbdb93623e06b0e2701c12b88. --- .../phases/3-transform/client/visitors/template.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 f80caa250594..d544e9d3992d 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 @@ -1775,7 +1775,7 @@ export const template_visitors = { /** @type {import('estree').Expression[]} */ const args = [context.state.node]; - for (const arg of node.arguments) { + node.arguments.forEach((arg) => { if (arg.type === 'SpreadElement') { // this is a spread operation, meaning we need to thunkify all of its members, which we can't // do until runtime @@ -1787,7 +1787,7 @@ export const template_visitors = { return; } args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(arg)))); - } + }); let snippet_function = /** @type {import('estree').Expression} */ ( context.visit(node.expression) @@ -2483,9 +2483,7 @@ export const template_visitors = { /** @type {import('estree').Statement[]} */ const declarations = []; - for (let i = 0; i < node.context.elements.length; i++) { - const argument = node.context.elements[i]; - + node.context.elements.forEach((argument, i) => { if (!argument) return; if (argument.type === 'Identifier') { @@ -2549,7 +2547,7 @@ export const template_visitors = { binding.expression = b.call(name); } - } + }); body = b.block([ ...declarations, From 11086f30ce1d73c504bfa6de5788d3f92fae33e5 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 16:46:26 -0700 Subject: [PATCH 43/76] fix: My own stupidity --- .../3-transform/client/visitors/template.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) 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 d544e9d3992d..cda8d0d3c8b3 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 @@ -1775,7 +1775,7 @@ export const template_visitors = { /** @type {import('estree').Expression[]} */ const args = [context.state.node]; - node.arguments.forEach((arg) => { + for (const arg of node.arguments) { if (arg.type === 'SpreadElement') { // this is a spread operation, meaning we need to thunkify all of its members, which we can't // do until runtime @@ -1784,10 +1784,10 @@ export const template_visitors = { context.visit(b.spread(b.call('$.thunkspread', arg.argument))) ) ); - return; + continue; } args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(arg)))); - }); + } let snippet_function = /** @type {import('estree').Expression} */ ( context.visit(node.expression) @@ -2483,8 +2483,10 @@ export const template_visitors = { /** @type {import('estree').Statement[]} */ const declarations = []; - node.context.elements.forEach((argument, i) => { - if (!argument) return; + for (let i = 0; i < node.context.elements.length; i++) { + const argument = node.context.elements[i]; + + if (!argument) continue; if (argument.type === 'Identifier') { args.push(argument); @@ -2495,7 +2497,7 @@ export const template_visitors = { // this expression appears on the left side of an assignment somewhere. For example: // `$.maybe_call(myArg).value = 1` is valid JavaScript, but `$.myArg?.().value = 1` is not binding.expression = b.call('$.maybe_call', argument); - return; + continue; } let arg_alias = `$$arg${i}`; @@ -2507,7 +2509,7 @@ export const template_visitors = { declarations.push( b.let(argument.argument.name, b.call('$.proxy_rest_array', b.id(arg_alias))) ); - return; + continue; } const new_arg_alias = `$$proxied_arg${i}`; @@ -2547,7 +2549,7 @@ export const template_visitors = { binding.expression = b.call(name); } - }); + } body = b.block([ ...declarations, From f9ac0cf39cd4b8d02e923132d6624264ea08ffaa Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 16:52:04 -0700 Subject: [PATCH 44/76] fix: style --- .../src/routes/docs/content/01-api/03-snippets.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md index b86081d6f579..fab420147812 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md @@ -237,10 +237,12 @@ You can import the `Snippet` type from `'svelte'`: The `Snippet` type is generic. Here's how you'd type various cases: ```ts -type SnippetWithNoArgs = Snippet -type SnippetWithOneArg = Snippet<[argOne: number]> -type SnippetWithMultipleArgs = Snippet<[argOne: number, argTwo: string]> -type SnippetWithAnyNumberOfArgs = Snippet +type SnippetWithNoArgs = Snippet; +type SnippetWithOneArg = Snippet<[argOne: number]>; +type SnippetWithMultipleArgs = Snippet< + [argOne: number, argTwo: string] +>; +type SnippetWithAnyNumberOfArgs = Snippet; ``` And here are the snippet declarations matching those cases (note: this example uses TypeScript): From 46306700d2aed89aa19efbe025f3e278f9ce21bc Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Tue, 9 Jan 2024 17:03:53 -0700 Subject: [PATCH 45/76] fix - maybe --- .../src/routes/docs/content/01-api/03-snippets.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md index fab420147812..c9291f6273af 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md @@ -237,6 +237,8 @@ You can import the `Snippet` type from `'svelte'`: The `Snippet` type is generic. Here's how you'd type various cases: ```ts +import type { Snippet } from 'svelte'; + type SnippetWithNoArgs = Snippet; type SnippetWithOneArg = Snippet<[argOne: number]>; type SnippetWithMultipleArgs = Snippet< From 25849313174bdcc97a4d94cb7a5cb2400689cadf Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 10 Jan 2024 10:21:04 +0100 Subject: [PATCH 46/76] Update sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md --- .../src/routes/docs/content/01-api/03-snippets.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md index c9291f6273af..92c52823369c 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md @@ -237,6 +237,7 @@ You can import the `Snippet` type from `'svelte'`: The `Snippet` type is generic. Here's how you'd type various cases: ```ts +// @errors: 2305 import type { Snippet } from 'svelte'; type SnippetWithNoArgs = Snippet; From a6a7a7466c4532d73cf143437ade1e22cc6f9dd7 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Wed, 10 Jan 2024 22:22:24 -0700 Subject: [PATCH 47/76] Update tag.js Co-authored-by: Rich Harris --- packages/svelte/src/compiler/phases/1-parse/state/tag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e5163aad7d77..f33cf5db6b12 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -266,7 +266,7 @@ function open(parser) { } if (parser.match('snippet')) { - const snippet_declaraion_end = find_matching_bracket(parser.template, parser.index, '{'); + const snippet_declaration_end = find_matching_bracket(parser.template, parser.index, '{'); if (!snippet_declaraion_end) { error(start, 'TODO', 'Expected a closing curly bracket'); From 7b32431a9f999a7ce3e305aec2cf6498afe044a2 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 11 Jan 2024 14:39:41 -0700 Subject: [PATCH 48/76] Update .changeset/curvy-cups-cough.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/curvy-cups-cough.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/curvy-cups-cough.md b/.changeset/curvy-cups-cough.md index 5185886b0225..e16cb8377740 100644 --- a/.changeset/curvy-cups-cough.md +++ b/.changeset/curvy-cups-cough.md @@ -2,4 +2,4 @@ 'svelte': patch --- -feat: snippets can now take multiple arguments +breaking: snippets can now take multiple arguments, support default parameters. Because of this, the type signature has changed From 37b6fd48a25aa5bcaca3160a8d1f39741d389e5f Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 11 Jan 2024 15:12:26 -0700 Subject: [PATCH 49/76] chore: Remove rest params --- .../src/compiler/phases/1-parse/state/tag.js | 8 +++ .../3-transform/client/visitors/template.js | 28 +--------- .../svelte/src/internal/client/runtime.js | 26 --------- .../src/internal/client/runtime.test.ts | 24 +------- packages/svelte/src/internal/index.js | 1 - packages/svelte/src/main/public.d.ts | 22 +++++--- .../_config.js | 21 ------- .../main.svelte | 15 ----- .../samples/snippet-argument-rest/_config.js | 21 ------- .../samples/snippet-argument-rest/main.svelte | 15 ----- .../samples/snippet-spread-args/main.svelte | 4 +- packages/svelte/types/index.d.ts | 56 ++++++++++++------- .../routes/docs/content/01-api/03-snippets.md | 7 +-- 13 files changed, 66 insertions(+), 182 deletions(-) delete mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/main.svelte delete mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/_config.js delete mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/main.svelte 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 e5163aad7d77..b32e15c7f68e 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -298,6 +298,14 @@ function open(parser) { error(snippet_expression, 'TODO', 'expected a snippet name'); } + if (snippet_expression.params.at(-1)?.type === 'RestElement') { + error( + snippet_expression, + 'TODO', + 'snippets do not support rest parameters; use an array instead' + ); + } + // slice the `{#` off the beginning since it's already been eaten parser.eat(raw_snippet_declaration.slice(2), true); 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 cda8d0d3c8b3..ee7f8ec3699b 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 @@ -2501,25 +2501,9 @@ export const template_visitors = { } let arg_alias = `$$arg${i}`; + args.push(b.id(arg_alias)); - if (argument.type === 'RestElement') { - args.push(b.rest(b.id(arg_alias))); - - if (argument.argument.type === 'Identifier') { - declarations.push( - b.let(argument.argument.name, b.call('$.proxy_rest_array', b.id(arg_alias))) - ); - continue; - } - - const new_arg_alias = `$$proxied_arg${i}`; - declarations.push(b.let(new_arg_alias, b.call('$.proxy_rest_array', b.id(arg_alias)))); - arg_alias = new_arg_alias; - } else { - args.push(b.id(arg_alias)); - } - - const paths = extract_paths(argument.type === 'RestElement' ? argument.argument : argument); + const paths = extract_paths(argument); for (const path of paths) { const name = /** @type {import('estree').Identifier} */ (path.node).name; @@ -2529,13 +2513,7 @@ export const template_visitors = { path.node, b.thunk( /** @type {import('estree').Expression} */ ( - context.visit( - path.expression?.( - argument.type === 'RestElement' - ? b.id(arg_alias) - : b.call('$.maybe_call', b.id(arg_alias)) - ) - ) + context.visit(path.expression?.(b.call('$.maybe_call', b.id(arg_alias)))) ) ) ) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 075c5a41fa4b..2329d29e69d1 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -2028,32 +2028,6 @@ export function thunkspread(iterable) { return thunks; } -/** - * This is meant to proxy the `...rest` parameter to a snippet function. Basically, - * this array will be full of functions that need to be invoked to unwrap their value. - * We have no way of forcing that invocation in all circumstances -- for example, if - * a user passes the rest array to a function that then accesses it via `rest[0]`, we - * would need to transform that into `rest[0]()`. That's effectively what this proxy does. - * - * @template {unknown[]} T - * @param {T} items - * @returns {T} - */ -export function proxy_rest_array(items) { - return new Proxy(items, { - get(target, property) { - // @ts-expect-error -- It thinks arrays can't have properties that aren't numeric - if (typeof property === 'symbol') return target[property]; - if (!isNaN(parseInt(property))) { - // @ts-expect-error -- It thinks arrays can't have properties that aren't numeric - return target[property]?.(); - } - // @ts-expect-error -- It thinks arrays can't have properties that aren't numeric - return target[property]; - } - }); -} - /** * @template {Function | undefined} T * @param {T} fn diff --git a/packages/svelte/src/internal/client/runtime.test.ts b/packages/svelte/src/internal/client/runtime.test.ts index ecd19102cc18..302d4ffb9844 100644 --- a/packages/svelte/src/internal/client/runtime.test.ts +++ b/packages/svelte/src/internal/client/runtime.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { proxy_rest_array, thunkspread } from '..'; +import { thunkspread } from '..'; describe('thunkspread', () => { it('makes all of its arguments callable', () => { @@ -19,25 +19,3 @@ describe('thunkspread', () => { expect(thunks.map((thunk) => thunk())).toEqual([...items()]); }); }); - -describe('proxy_rest_array', () => { - it('calls its items on access', () => { - const items = [() => 1, () => 2, () => 3]; - const proxied_items = proxy_rest_array(items); - expect(proxied_items[1]).toBe(2); - }); - - it('returns undefined for keys with no item', () => { - const items = [() => 1, () => 2, () => 3]; - const proxied_items = proxy_rest_array(items); - expect(proxied_items[4]).toBe(undefined); - }); - - it('works with array methods', () => { - const items = [() => 1, () => 2, () => 3]; - const proxied_items = proxy_rest_array(items); - expect(proxied_items.map((item) => item)).toEqual([1, 2, 3]); - // @ts-expect-error - This is a weird case for sure - expect(proxied_items.find((item) => item === 1)).toBe(1); - }); -}); diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index a5f789ee7168..306bd88e8a46 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -37,7 +37,6 @@ export { user_root_effect, inspect, unwrap, - proxy_rest_array, thunkspread, maybe_call, freeze diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts index 75415ab3bcc8..dfb4f6b38a01 100644 --- a/packages/svelte/src/main/public.d.ts +++ b/packages/svelte/src/main/public.d.ts @@ -195,14 +195,20 @@ declare const SnippetReturn: unique symbol; * ``` * You can only call a snippet through the `{@render ...}` tag. */ -export interface Snippet { - ( - this: void, - ...args: T - ): typeof SnippetReturn & { - _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; - }; -} +export type Snippet = + // this conditional allows tuples but not arrays. Arrays would indicate a + // rest parameter type, which is not supported. If rest parameters are added + // in the future, the condition can be removed. + number extends T['length'] + ? never + : { + ( + this: void, + ...args: T + ): typeof SnippetReturn & { + _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; + }; + }; interface DispatchOptions { cancelable?: boolean; diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/_config.js deleted file mode 100644 index 6c0f9cbb31ee..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/_config.js +++ /dev/null @@ -1,21 +0,0 @@ -import { test } from '../../test'; - -export default test({ - html: ` -

clicks: 0, doubled: 0, tripled: 0

- - `, - - async test({ assert, target }) { - const btn = target.querySelector('button'); - - await btn?.click(); - assert.htmlEqual( - target.innerHTML, - ` -

clicks: 1, doubled: 2, tripled: 3

- - ` - ); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/main.svelte deleted file mode 100644 index 263f399a0518..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest-destructured/main.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - -{#snippet foo(n: number, ...[doubled, { tripled }]: number[])} -

clicks: {n}, doubled: {doubled}, tripled: {tripled}

-{/snippet} - -{@render foo(count, doubled, {tripled})} - - diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/_config.js deleted file mode 100644 index 6c0f9cbb31ee..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/_config.js +++ /dev/null @@ -1,21 +0,0 @@ -import { test } from '../../test'; - -export default test({ - html: ` -

clicks: 0, doubled: 0, tripled: 0

- - `, - - async test({ assert, target }) { - const btn = target.querySelector('button'); - - await btn?.click(); - assert.htmlEqual( - target.innerHTML, - ` -

clicks: 1, doubled: 2, tripled: 3

- - ` - ); - } -}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/main.svelte deleted file mode 100644 index 05ac668a2e12..000000000000 --- a/packages/svelte/tests/runtime-runes/samples/snippet-argument-rest/main.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - -{#snippet foo(n: number, ...rest: number[])} -

clicks: {n}, doubled: {rest[0]}, tripled: {rest[1]}

-{/snippet} - -{@render foo(count, doubled, tripled)} - - diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/main.svelte index b2f2ae13e2dc..06c6942c6c12 100644 --- a/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/snippet-spread-args/main.svelte @@ -27,8 +27,8 @@ let whatever_comes_after_that = derivedBox(count, 5); -{#snippet foo(n: number, ...[doubled, { tripled }, ...rest]: number[])} -

clicks: {n.value}, doubled: {doubled.value}, tripled: {tripled.value}, quadrupled: {rest[0].value}, something else: {rest[1].value}

+{#snippet foo(n, doubled, { tripled }, quadrupled, whatever)} +

clicks: {n.value}, doubled: {doubled.value}, tripled: {tripled.value}, quadrupled: {quadrupled.value}, something else: {whatever.value}

{/snippet} {@render foo(...[count, doubled, {tripled}, quadrupled, whatever_comes_after_that])} diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index fcd4b24dbe69..98461ce7317d 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -196,14 +196,20 @@ declare module 'svelte' { * ``` * You can only call a snippet through the `{@render ...}` tag. */ - export interface Snippet { - ( - this: void, - ...args: T - ): typeof SnippetReturn & { - _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; - }; - } + export type Snippet = + // this conditional allows tuples but not arrays. Arrays would indicate a + // rest parameter type, which is not supported. If rest parameters are added + // in the future, the condition can be removed. + number extends T['length'] + ? never + : { + ( + this: void, + ...args: T + ): typeof SnippetReturn & { + _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; + }; + }; interface DispatchOptions { cancelable?: boolean; @@ -324,7 +330,9 @@ declare module 'svelte' { new (options: ComponentConstructorOptions | undefined; + children?: ((this: void) => unique symbol & { + _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\""; + }) | undefined; })>): SvelteComponent; }, options: { target: Node; @@ -347,7 +355,9 @@ declare module 'svelte' { new (options: ComponentConstructorOptions | undefined; + children?: ((this: void) => unique symbol & { + _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\""; + }) | undefined; })>): SvelteComponent; }, options: { target: Node; @@ -1725,7 +1735,9 @@ declare module 'svelte/legacy' { } ? {} : Slots extends { default: any; } ? { - children?: Snippet<[]> | undefined; + children?: ((this: void) => unique symbol & { + _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\""; + }) | undefined; } : {})>): SvelteComponent; } & Exports; // This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are). @@ -1851,14 +1863,20 @@ declare module 'svelte/legacy' { * ``` * You can only call a snippet through the `{@render ...}` tag. */ - interface Snippet { - ( - this: void, - ...args: T - ): typeof SnippetReturn & { - _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; - }; - } + type Snippet = + // this conditional allows tuples but not arrays. Arrays would indicate a + // rest parameter type, which is not supported. If rest parameters are added + // in the future, the condition can be removed. + number extends T['length'] + ? never + : { + ( + this: void, + ...args: T + ): typeof SnippetReturn & { + _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; + }; + }; } declare module 'svelte/motion' { diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md index c9291f6273af..6cc64c8977b0 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md @@ -58,7 +58,7 @@ Snippets, and _render tags_, are a way to create reusable chunks of markup insid {/each} ``` -A snippet behaves pretty much like a regular function declaration: It can have multiple parameters, those parameters can be destructured, and they can have default values. You can also use `...rest` params. ([demo](/#H4sIAAAAAAAAE2WO0YrCQAxFfyXGhVoo9L3Wot9hF6xt1IHpTJikggzz78tI2YX1MTecc2_Em7Ek2JwjumEmbPDEjBXqi_MhT7JKWKH4JYw5aWUMhrXrXa-WFCLMJDLcCQ5wMVoIOK8gHu6BBgX1IETw8ssFEhzgi4Nn2ZX73rX1n8rFrTjDTAoPstbv8pjqQ_3hLFPe0XL3piBmLG0grmDatDV3vYv1ak_vrmMgN1FYq4rBmpGKrPr_ufpr8buiTFjh7CdzMzRho2Gh9J1-AFhYxBNCAQAA)): +A snippet behaves pretty much like a regular function declaration: It can have multiple parameters, those parameters can be destructured, and they can have default values. However, you cannot use `...rest` params. ([demo](/#H4sIAAAAAAAAE2WO0YrCQAxFfyXGhVoo9L3Wot9hF6xt1IHpTJikggzz78tI2YX1MTecc2_Em7Ek2JwjumEmbPDEjBXqi_MhT7JKWKH4JYw5aWUMhrXrXa-WFCLMJDLcCQ5wMVoIOK8gHu6BBgX1IETw8ssFEhzgi4Nn2ZX73rX1n8rFrTjDTAoPstbv8pjqQ_3hLFPe0XL3piBmLG0grmDatDV3vYv1ak_vrmMgN1FYq4rBmpGKrPr_ufpr8buiTFjh7CdzMzRho2Gh9J1-AFhYxBNCAQAA)): ```svelte {#snippet figure({ src, caption, width, height })} @@ -244,7 +244,6 @@ type SnippetWithOneArg = Snippet<[argOne: number]>; type SnippetWithMultipleArgs = Snippet< [argOne: number, argTwo: string] >; -type SnippetWithAnyNumberOfArgs = Snippet; ``` And here are the snippet declarations matching those cases (note: this example uses TypeScript): @@ -261,8 +260,4 @@ And here are the snippet declarations matching those cases (note: this example u {#snippet withMultipleArgs(argOne: number, argTwo: string)} {/snippet} - -{#snippet withAnyNumberOfArgs(...rest: number[])} - -{/snippet} ``` From 63b037486a69ef617e060cb558a574e1f4b70ac7 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:07:57 +0100 Subject: [PATCH 50/76] Delete .changeset/eighty-rivers-wash.md --- .changeset/eighty-rivers-wash.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/eighty-rivers-wash.md diff --git a/.changeset/eighty-rivers-wash.md b/.changeset/eighty-rivers-wash.md deleted file mode 100644 index 4fa81fc2e65c..000000000000 --- a/.changeset/eighty-rivers-wash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -feat: snippets now support default parameter values for regular and destructured parameters From 39d6ed05aa1e77f2f68e1415a6bb94d0ea50b81f Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 14 Jan 2024 17:52:00 -0700 Subject: [PATCH 51/76] fix: Honestly idk why it was broken but it's fixed now --- packages/svelte/src/main/private.d.ts | 12 ++++++ packages/svelte/src/main/public.d.ts | 9 +---- packages/svelte/types/index.d.ts | 57 +++++++++------------------ 3 files changed, 31 insertions(+), 47 deletions(-) diff --git a/packages/svelte/src/main/private.d.ts b/packages/svelte/src/main/private.d.ts index 54f9869178f1..a56022329778 100644 --- a/packages/svelte/src/main/private.d.ts +++ b/packages/svelte/src/main/private.d.ts @@ -1,4 +1,16 @@ +import type { Snippet } from './public'; + /** * Anything except a function */ export type NotFunction = T extends Function ? never : T; + +// Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already +// if you're curious why this is here and not declared as an unexported type from `public.d.ts`, try putting it there +// and see what happens in the output `index.d.ts` -- it breaks, presumably because of a TypeScript compiler bug. +export type PropsWithChildren = Props & + (Props extends { children?: any } + ? {} + : Slots extends { default: any } + ? { children?: Snippet<[]> } + : {}); diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts index dfb4f6b38a01..17f6d3b2a5d4 100644 --- a/packages/svelte/src/main/public.d.ts +++ b/packages/svelte/src/main/public.d.ts @@ -18,14 +18,6 @@ export interface ComponentConstructorOptions< $$inline?: boolean; } -// Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already -type PropsWithChildren = Props & - (Props extends { children?: any } - ? {} - : Slots extends { default: any } - ? { children?: Snippet } - : {}); - /** * Can be used to create strongly typed Svelte components. * @@ -229,3 +221,4 @@ export interface EventDispatcher> { export * from './main-client.js'; import './ambient.js'; +import type { PropsWithChildren } from './private.js'; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 98461ce7317d..ab90811cf8ac 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -19,14 +19,6 @@ declare module 'svelte' { $$inline?: boolean; } - // Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already - type PropsWithChildren = Props & - (Props extends { children?: any } - ? {} - : Slots extends { default: any } - ? { children?: Snippet } - : {}); - /** * Can be used to create strongly typed Svelte components. * @@ -319,6 +311,14 @@ declare module 'svelte' { * Anything except a function */ type NotFunction = T extends Function ? never : T; + + // Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already + type PropsWithChildren = Props & + (Props extends { children?: any } + ? {} + : Slots extends { default: any } + ? { children?: Snippet<[]> } + : {}); /** * Mounts the given component to the given target and returns a handle to the component's public accessors * as well as a `$set` and `$destroy` method to update the props of the component or destroy it. @@ -327,13 +327,7 @@ declare module 'svelte' { * * */ export function createRoot, Exports extends Record | undefined, Events extends Record>(component: { - new (options: ComponentConstructorOptions unique symbol & { - _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\""; - }) | undefined; - })>): SvelteComponent; + new (options: ComponentConstructorOptions>): SvelteComponent; }, options: { target: Node; props?: Props | undefined; @@ -352,13 +346,7 @@ declare module 'svelte' { * * */ export function mount, Exports extends Record | undefined, Events extends Record>(component: { - new (options: ComponentConstructorOptions unique symbol & { - _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\""; - }) | undefined; - })>): SvelteComponent; + new (options: ComponentConstructorOptions>): SvelteComponent; }, options: { target: Node; props?: Props | undefined; @@ -1730,15 +1718,7 @@ declare module 'svelte/legacy' { * * */ export function asClassComponent, Exports extends Record, Events extends Record, Slots extends Record>(component: SvelteComponent): { - new (options: ComponentConstructorOptions unique symbol & { - _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\""; - }) | undefined; - } : {})>): SvelteComponent; + new (options: ComponentConstructorOptions>): SvelteComponent; } & Exports; // This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are). @@ -1760,14 +1740,6 @@ declare module 'svelte/legacy' { $$inline?: boolean; } - // Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already - type PropsWithChildren = Props & - (Props extends { children?: any } - ? {} - : Slots extends { default: any } - ? { children?: Snippet } - : {}); - /** * Can be used to create strongly typed Svelte components. * @@ -1877,6 +1849,13 @@ declare module 'svelte/legacy' { _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; }; }; + // Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already + type PropsWithChildren = Props & + (Props extends { children?: any } + ? {} + : Slots extends { default: any } + ? { children?: Snippet<[]> } + : {}); } declare module 'svelte/motion' { From d1f4ed9608f2b2974bffc32fdbf4dc463fd55cd9 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 14 Jan 2024 18:04:20 -0700 Subject: [PATCH 52/76] fix: var name lol --- packages/svelte/src/compiler/phases/1-parse/state/tag.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5b8b91a4d01f..687262576c07 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -268,12 +268,12 @@ function open(parser) { if (parser.match('snippet')) { const snippet_declaration_end = find_matching_bracket(parser.template, parser.index, '{'); - if (!snippet_declaraion_end) { + if (!snippet_declaration_end) { error(start, 'TODO', 'Expected a closing curly bracket'); } // we'll eat this later, so save it - const raw_snippet_declaration = parser.template.slice(start, snippet_declaraion_end + 1); + const raw_snippet_declaration = parser.template.slice(start, snippet_declaration_end + 1); const start_subtractions = '{#snippet '; const end_subtractions = '}'; From 3f9a246144139795a25993ef81f5b3547217d402 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 14 Jan 2024 18:10:12 -0700 Subject: [PATCH 53/76] fix: typegen --- packages/svelte/types/index.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index cdcf1e66a97e..5cec96541eba 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -313,6 +313,8 @@ declare module 'svelte' { type NotFunction = T extends Function ? never : T; // Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already + // if you're curious why this is here and not declared as an unexported type from `public.d.ts`, try putting it there + // and see what happens in the output `index.d.ts` -- it breaks, presumably because of a TypeScript compiler bug. type PropsWithChildren = Props & (Props extends { children?: any } ? {} @@ -1848,6 +1850,8 @@ declare module 'svelte/legacy' { }; }; // Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already + // if you're curious why this is here and not declared as an unexported type from `public.d.ts`, try putting it there + // and see what happens in the output `index.d.ts` -- it breaks, presumably because of a TypeScript compiler bug. type PropsWithChildren = Props & (Props extends { children?: any } ? {} From 4c8431328eedd572bd77bc883b452b40acee7bb9 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 14 Jan 2024 18:10:50 -0700 Subject: [PATCH 54/76] fix: idk --- packages/svelte/src/main/public.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts index e71779671184..17f6d3b2a5d4 100644 --- a/packages/svelte/src/main/public.d.ts +++ b/packages/svelte/src/main/public.d.ts @@ -214,8 +214,8 @@ export interface EventDispatcher> { ...args: null extends EventMap[Type] ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions] : undefined extends EventMap[Type] - ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions] - : [type: Type, parameter: EventMap[Type], options?: DispatchOptions] + ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions] + : [type: Type, parameter: EventMap[Type], options?: DispatchOptions] ): boolean; } From ab851d5627168686395e37d7740b76485e98ce7e Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 14 Jan 2024 18:14:47 -0700 Subject: [PATCH 55/76] fix: It looks like a bunch of unformatted shit came in through main?? idk --- packages/svelte/src/compiler/errors.js | 6 +-- packages/svelte/src/compiler/legacy.js | 2 +- .../compiler/phases/1-parse/state/element.js | 16 ++++---- .../phases/1-parse/utils/mapped_code.js | 4 +- .../src/compiler/phases/2-analyze/index.js | 12 +++--- .../3-transform/client/transform-client.js | 2 +- .../phases/3-transform/client/utils.js | 8 ++-- .../client/visitors/javascript-runes.js | 7 +--- .../3-transform/client/visitors/template.js | 40 +++++++++---------- .../3-transform/server/transform-server.js | 22 +++++----- .../src/compiler/phases/3-transform/utils.js | 2 +- .../svelte/src/compiler/preprocess/index.js | 2 +- .../svelte/src/compiler/utils/mapped_code.js | 4 +- packages/svelte/src/easing/index.js | 12 +++--- packages/svelte/src/internal/client/each.js | 8 ++-- packages/svelte/src/internal/client/render.js | 12 +++--- .../svelte/src/internal/client/runtime.js | 8 ++-- .../svelte/src/internal/client/transitions.js | 4 +- .../svelte/src/internal/client/validate.js | 4 +- packages/svelte/src/store/index.js | 2 +- packages/svelte/svelte-html.d.ts | 8 ++-- packages/svelte/tests/suite.ts | 2 +- .../src/lib/workers/bundler/index.js | 2 +- .../src/routes/auth/login/+server.js | 2 +- 24 files changed, 94 insertions(+), 97 deletions(-) diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 25a21d8402f8..c083604b87c1 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -234,8 +234,8 @@ const attributes = { type === 'no-each' ? `An element that uses the animate directive must be the immediate child of a keyed each block` : type === 'each-key' - ? `An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?` - : `An element that uses the animate directive must be the sole child of a keyed each block`, + ? `An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?` + : `An element that uses the animate directive must be the sole child of a keyed each block`, 'duplicate-animation': () => `An element can only have one 'animate' directive`, /** @param {string[] | undefined} [modifiers] */ 'invalid-event-modifier': (modifiers) => @@ -262,7 +262,7 @@ const attributes = { ? `An element can only have one '${directive1}' directive` : `An element cannot have both ${describe(directive1)} directive and ${describe( directive2 - )} directive`; + )} directive`; }, 'invalid-let-directive-placement': () => 'let directive at invalid position' }; diff --git a/packages/svelte/src/compiler/legacy.js b/packages/svelte/src/compiler/legacy.js index e8595fd52f9e..8d67f654c2c6 100644 --- a/packages/svelte/src/compiler/legacy.js +++ b/packages/svelte/src/compiler/legacy.js @@ -108,7 +108,7 @@ export function convert(source, ast) { // @ts-ignore delete node.parent; } - }) + }) : undefined }; }, diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index 5f2eda1f17b5..3c479ade83e1 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -108,12 +108,12 @@ export default function tag(parser) { const type = meta_tags.has(name) ? meta_tags.get(name) : regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component' - ? 'Component' - : name === 'title' && parent_is_head(parser.stack) - ? 'TitleElement' - : name === 'slot' - ? 'SlotElement' - : 'RegularElement'; + ? 'Component' + : name === 'title' && parent_is_head(parser.stack) + ? 'TitleElement' + : name === 'slot' + ? 'SlotElement' + : 'RegularElement'; /** @type {import('#compiler').ElementLike} */ // @ts-expect-error TODO can't figure out this error @@ -131,7 +131,7 @@ export default function tag(parser) { has_spread: false }, parent: null - } + } : { type: /** @type {import('#compiler').ElementLike['type']} */ (type), start, @@ -140,7 +140,7 @@ export default function tag(parser) { attributes: [], fragment: create_fragment(true), parent: null - }; + }; parser.allow_whitespace(); diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js b/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js index 95f136effa72..01be1b492f62 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js @@ -275,7 +275,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { sourcemap_list, () => null, true // skip optional field `sourcesContent` - ) + ) : remapping( // use loader interface sourcemap_list[0], // last map @@ -291,7 +291,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { } ), true - ); + ); if (!map.file) delete map.file; // skip optional field `file` diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 0c207f95080b..98c148a3380c 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -710,12 +710,12 @@ const runes_scope_tweaker = { rune === '$state' ? 'state' : rune === '$state.frozen' - ? 'frozen_state' - : rune === '$derived' - ? 'derived' - : path.is_rest - ? 'rest_prop' - : 'prop'; + ? 'frozen_state' + : rune === '$derived' + ? 'derived' + : path.is_rest + ? 'rest_prop' + : 'prop'; } if (rune === '$props') { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 1dfafd64b45c..27069478c896 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -201,7 +201,7 @@ export function client_component(source, analysis, options) { b.call('$.validate_store', store_reference, b.literal(name.slice(1))), store_get ]) - ) + ) : b.thunk(store_get) ) ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 7490636a218d..d8e600808625 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -20,11 +20,11 @@ export function get_assignment_value(node, { state, visit }) { return operator === '=' ? /** @type {import('estree').Expression} */ (visit(node.right)) : // turn something like x += 1 into x = x + 1 - b.binary( + b.binary( /** @type {import('estree').BinaryOperator} */ (operator.slice(0, -1)), serialize_get_binding(node.left, state), /** @type {import('estree').Expression} */ (visit(node.right)) - ); + ); } else if ( node.left.type === 'MemberExpression' && node.left.object.type === 'ThisExpression' && @@ -35,11 +35,11 @@ export function get_assignment_value(node, { state, visit }) { return operator === '=' ? /** @type {import('estree').Expression} */ (visit(node.right)) : // turn something like x += 1 into x = x + 1 - b.binary( + b.binary( /** @type {import('estree').BinaryOperator} */ (operator.slice(0, -1)), /** @type {import('estree').Expression} */ (visit(node.left)), /** @type {import('estree').Expression} */ (visit(node.right)) - ); + ); } else { return /** @type {import('estree').Expression} */ (visit(node.right)); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js index 0150c30293ad..63c46d8c6464 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js @@ -87,11 +87,8 @@ export const javascript_visitors_runes = { field.kind === 'state' ? b.call('$.source', should_proxy_or_freeze(init) ? b.call('$.proxy', init) : init) : field.kind === 'frozen_state' - ? b.call( - '$.source', - should_proxy_or_freeze(init) ? b.call('$.freeze', init) : init - ) - : b.call('$.derived', b.thunk(init)); + ? b.call('$.source', should_proxy_or_freeze(init) ? b.call('$.freeze', init) : init) + : b.call('$.derived', b.thunk(init)); } else { // if no arguments, we know it's state as `$derived()` is a compile error value = b.call('$.source'); 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 4c9115f09ac1..f1902ee8f643 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 @@ -655,15 +655,15 @@ function serialize_element_special_value_attribute(element, node_id, attribute, // This ensures things stay in sync with the select binding // in case of updates to the option value or new values appearing b.call('$.selected', node_id) - ]) + ]) : needs_option_call - ? b.sequence([ - inner_assignment, - // This ensures a one-way street to the DOM in case it's - b.call('$.select_option', node_id, value) - ]) - : inner_assignment + ? b.sequence([ + inner_assignment, + // This ensures a one-way street to the DOM in case it's + b.call('$.select_option', node_id, value) + ]) + : inner_assignment ); if (is_reactive) { @@ -953,7 +953,7 @@ function serialize_inline_component(node, component_name, context) { : b.call( '$.spread_props', ...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p)) - ); + ); /** @param {import('estree').Identifier} node_id */ let fn = (node_id) => b.call( @@ -2349,7 +2349,7 @@ export const template_visitors = { ? b.arrow( [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.fallback)) - ) + ) : b.literal(null); const key_function = node.key && ((each_type & EACH_ITEM_REACTIVE) !== 0 || context.state.options.dev) @@ -2360,7 +2360,7 @@ export const template_visitors = { b.return(/** @type {import('estree').Expression} */ (context.visit(node.key))) ) ) - ) + ) : b.literal(null); if (node.index && each_node_meta.contains_group_binding) { @@ -2422,7 +2422,7 @@ export const template_visitors = { ? b.arrow( [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.alternate)) - ) + ) : b.literal(null) ) ) @@ -2441,7 +2441,7 @@ export const template_visitors = { ? b.arrow( [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.pending)) - ) + ) : b.literal(null), node.then ? b.arrow( @@ -2449,10 +2449,10 @@ export const template_visitors = { ? [ b.id('$$anchor'), /** @type {import('estree').Pattern} */ (context.visit(node.value)) - ] + ] : [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.then)) - ) + ) : b.literal(null), node.catch ? b.arrow( @@ -2460,10 +2460,10 @@ export const template_visitors = { ? [ b.id('$$anchor'), /** @type {import('estree').Pattern} */ (context.visit(node.error)) - ] + ] : [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.catch)) - ) + ) : b.literal(null) ) ) @@ -2885,9 +2885,9 @@ export const template_visitors = { /** @type {import('estree').Expression} */ (node.expression).type === 'ObjectExpression' ? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine - b.object_pattern(node.expression.properties) + b.object_pattern(node.expression.properties) : // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine - b.array_pattern(node.expression.elements), + b.array_pattern(node.expression.elements), b.member(b.id('$$slotProps'), b.id(node.name)) ), b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node)))) @@ -2980,7 +2980,7 @@ export const template_visitors = { : b.arrow( [b.id('$$anchor')], b.block(create_block(node, 'fallback', node.fragment.nodes, context)) - ); + ); const expression = is_default ? b.member(b.id('$$props'), b.id('children')) 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 f6fab25b9cfd..03f8f596cc12 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 @@ -351,11 +351,11 @@ function get_assignment_value(node, { state, visit }) { return operator === '=' ? /** @type {import('estree').Expression} */ (visit(node.right)) : // turn something like x += 1 into x = x + 1 - b.binary( + b.binary( /** @type {import('estree').BinaryOperator} */ (operator.slice(0, -1)), serialize_get_binding(node.left, state), /** @type {import('estree').Expression} */ (visit(node.right)) - ); + ); } else { return /** @type {import('estree').Expression} */ (visit(node.right)); } @@ -780,7 +780,7 @@ function serialize_element_spread_attributes( b.id('join') ), b.literal(' ') - ) + ) : b.literal(''); args.push( b.object([ @@ -933,7 +933,7 @@ function serialize_inline_component(node, component_name, context) { : b.call( '$.spread_props', b.array(props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))) - ); + ); /** @type {import('estree').Statement} */ let statement = b.stmt( @@ -942,7 +942,7 @@ function serialize_inline_component(node, component_name, context) { ? b.call( '$.validate_component', typeof component_name === 'string' ? b.id(component_name) : component_name - ) + ) : component_name, b.id('$$payload'), props_expression @@ -1034,7 +1034,7 @@ const javascript_visitors_legacy = { '$.value_or_fallback', prop, /** @type {import('estree').Expression} */ (visit(declarator.init)) - ) + ) : prop; declarations.push(b.declarator(declarator.id, init)); @@ -1172,7 +1172,7 @@ const template_visitors = { template: [], init: [] } - } + } : { ...context, state }; const { hoisted, trimmed } = clean_nodes( @@ -1496,9 +1496,9 @@ const template_visitors = { b.let( node.expression.type === 'ObjectExpression' ? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine - b.object_pattern(node.expression.properties) + b.object_pattern(node.expression.properties) : // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine - b.array_pattern(node.expression.elements), + b.array_pattern(node.expression.elements), b.member(b.id('$$slotProps'), b.id(node.name)) ), b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node)))) @@ -1714,12 +1714,12 @@ function serialize_element_attributes(node, context) { ? b.call( b.member(attribute.expression, b.id('includes')), serialize_attribute_value(value_attribute.value, context) - ) + ) : b.binary( '===', attribute.expression, serialize_attribute_value(value_attribute.value, context) - ), + ), metadata: { contains_call_expression: false, dynamic: false diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index a924a59e1a4b..8ce6c0fd1ba0 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -198,7 +198,7 @@ export function infer_namespace(namespace, parent, nodes, path) { const parent_node = parent.type === 'Fragment' ? // Messy: We know that Fragment calls create_block directly, so we can do this here - path.at(-1) + path.at(-1) : parent; if ( diff --git a/packages/svelte/src/compiler/preprocess/index.js b/packages/svelte/src/compiler/preprocess/index.js index ad1061c78847..d12c87076083 100644 --- a/packages/svelte/src/compiler/preprocess/index.js +++ b/packages/svelte/src/compiler/preprocess/index.js @@ -318,7 +318,7 @@ async function process_markup(process, source) { string: processed.code, map: processed.map ? // TODO: can we use decode_sourcemap? - typeof processed.map === 'string' + typeof processed.map === 'string' ? JSON.parse(processed.map) : processed.map : undefined, diff --git a/packages/svelte/src/compiler/utils/mapped_code.js b/packages/svelte/src/compiler/utils/mapped_code.js index a438e48fd710..aebeaa1c5131 100644 --- a/packages/svelte/src/compiler/utils/mapped_code.js +++ b/packages/svelte/src/compiler/utils/mapped_code.js @@ -258,7 +258,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { sourcemap_list, () => null, true // skip optional field `sourcesContent` - ) + ) : remapping( // use loader interface sourcemap_list[0], // last map @@ -271,7 +271,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { } }, true - ); + ); if (!map.file) delete map.file; // skip optional field `file` // When source maps are combined and the leading map is empty, sources is not set. // Add the filename to the empty array in this case. diff --git a/packages/svelte/src/easing/index.js b/packages/svelte/src/easing/index.js index 1c9ced01d6bc..ed752d80f23e 100644 --- a/packages/svelte/src/easing/index.js +++ b/packages/svelte/src/easing/index.js @@ -59,10 +59,10 @@ export function bounceOut(t) { return t < a ? 7.5625 * t2 : t < b - ? 9.075 * t2 - 9.9 * t + 3.4 - : t < c - ? ca * t2 - cb * t + cc - : 10.8 * t * t - 20.52 * t + 10.72; + ? 9.075 * t2 - 9.9 * t + 3.4 + : t < c + ? ca * t2 - cb * t + cc + : 10.8 * t * t - 20.52 * t + 10.72; } /** @@ -180,8 +180,8 @@ export function expoInOut(t) { return t === 0.0 || t === 1.0 ? t : t < 0.5 - ? +0.5 * Math.pow(2.0, 20.0 * t - 10.0) - : -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0; + ? +0.5 * Math.pow(2.0, 20.0 * t - 10.0) + : -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0; } /** diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js index 4c0e5dec7e91..e66574fba0e7 100644 --- a/packages/svelte/src/internal/client/each.js +++ b/packages/svelte/src/internal/client/each.js @@ -133,8 +133,8 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re array = is_array(maybe_array) ? maybe_array : maybe_array == null - ? [] - : Array.from(maybe_array); + ? [] + : Array.from(maybe_array); if (key_fn !== null) { keys = array.map(key_fn); } else if ((flags & EACH_KEYED) === 0) { @@ -777,8 +777,8 @@ function each_item_block(item, key, index, render_fn, flags) { const item_value = each_item_not_reactive ? item : (flags & EACH_IS_IMMUTABLE) === 0 - ? mutable_source(item) - : source(item); + ? mutable_source(item) + : source(item); const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); const block = create_each_item_block(item_value, index_value, key); diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index c466fddab3c7..16ab59cf6566 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -881,8 +881,8 @@ export function bind_resize_observer(dom, type, update) { type === 'contentRect' || type === 'contentBoxSize' ? resize_observer_content_box : type === 'borderBoxSize' - ? resize_observer_border_box - : resize_observer_device_pixel_content_box; + ? resize_observer_border_box + : resize_observer_device_pixel_content_box; const unsub = observer.observe(dom, /** @param {any} entry */ (entry) => update(entry[type])); render_effect(() => unsub); } @@ -1606,8 +1606,8 @@ export function element(anchor_node, tag_fn, render_fn, is_svg = false) { ? current_hydration_fragment !== null ? /** @type {HTMLElement | SVGElement} */ (current_hydration_fragment[0]) : is_svg - ? document.createElementNS('http://www.w3.org/2000/svg', tag) - : document.createElement(tag) + ? document.createElementNS('http://www.w3.org/2000/svg', tag) + : document.createElement(tag) : null; const prev_element = element; if (prev_element !== null) { @@ -2869,7 +2869,7 @@ export function mount(component, options) { PassiveDelegatedEvents.includes(event_name) ? { passive: true - } + } : undefined ); // The document listener ensures we catch events that originate from elements that were @@ -2880,7 +2880,7 @@ export function mount(component, options) { PassiveDelegatedEvents.includes(event_name) ? { passive: true - } + } : undefined ); } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index b4ef2f3e7fe3..6ed46a753119 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -561,7 +561,7 @@ function infinite_loop_guard() { 'ERR_SVELTE_TOO_MANY_UPDATES' + (DEV ? ': Maximum update depth exceeded. This can happen when a reactive block or effect ' + - 'repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops.' + 'repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops.' : '') ); } @@ -1181,8 +1181,8 @@ export function set_signal_value(signal, value) { 'ERR_SVELTE_UNSAFE_MUTATION' + (DEV ? ": Unsafe mutations during Svelte's render or derived phase are not permitted in runes mode. " + - 'This can lead to unexpected errors and possibly cause infinite loops.\n\nIf this mutation is not meant ' + - 'to be reactive do not use the "$state" rune for that declaration.' + 'This can lead to unexpected errors and possibly cause infinite loops.\n\nIf this mutation is not meant ' + + 'to be reactive do not use the "$state" rune for that declaration.' : '') ); } @@ -1696,7 +1696,7 @@ export function safe_not_equal(a, b) { // eslint-disable-next-line eqeqeq return a != a ? // eslint-disable-next-line eqeqeq - b == b + b == b : a !== b || (a !== null && typeof a === 'object') || typeof a === 'function'; } diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 96ad2fd35a32..9a368d054bb5 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -542,10 +542,10 @@ export function bind_transition(dom, get_transition_fn, props_fn, direction, glo { from: /** @type {DOMRect} */ (from), to: dom.getBoundingClientRect() }, props, {} - ) + ) : /** @type {import('./types.js').TransitionFn} */ (transition_fn)(dom, props, { direction - }); + }); }); transition = create_transition(dom, init, direction, transition_effect); diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index c6f590a420ac..e3316891ebe3 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -75,8 +75,8 @@ export function validate_each_keys(collection, key_fn) { const array = is_array(maybe_array) ? maybe_array : maybe_array == null - ? [] - : Array.from(maybe_array); + ? [] + : Array.from(maybe_array); const length = array.length; for (let i = 0; i < length; i++) { const key = key_fn(array[i], i); diff --git a/packages/svelte/src/store/index.js b/packages/svelte/src/store/index.js index e18f7df8b231..857eab0af8e1 100644 --- a/packages/svelte/src/store/index.js +++ b/packages/svelte/src/store/index.js @@ -33,7 +33,7 @@ export function safe_not_equal(a, b) { // eslint-disable-next-line eqeqeq return a != a ? // eslint-disable-next-line eqeqeq - b == b + b == b : a !== b || (a && typeof a === 'object') || typeof a === 'function'; } diff --git a/packages/svelte/svelte-html.d.ts b/packages/svelte/svelte-html.d.ts index 614d1fb3bcc9..3547ad0d33b2 100644 --- a/packages/svelte/svelte-html.d.ts +++ b/packages/svelte/svelte-html.d.ts @@ -37,8 +37,8 @@ declare global { ): Key extends keyof ElementTagNameMap ? ElementTagNameMap[Key] : Key extends keyof SVGElementTagNameMap - ? SVGElementTagNameMap[Key] - : any; + ? SVGElementTagNameMap[Key] + : any; function createElement( // "undefined | null" because of element: Key | undefined | null, @@ -47,8 +47,8 @@ declare global { ): Key extends keyof ElementTagNameMap ? ElementTagNameMap[Key] : Key extends keyof SVGElementTagNameMap - ? SVGElementTagNameMap[Key] - : any; + ? SVGElementTagNameMap[Key] + : any; // For backwards-compatibility and ease-of-use, in case someone enhanced the typings from import('svelte/elements').HTMLAttributes/SVGAttributes // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/svelte/tests/suite.ts b/packages/svelte/tests/suite.ts index b694e4cd9578..34609d5e4aaf 100644 --- a/packages/svelte/tests/suite.ts +++ b/packages/svelte/tests/suite.ts @@ -17,7 +17,7 @@ const filter = process.env.FILTER process.env.FILTER.startsWith('/') ? process.env.FILTER.slice(1, -1).replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') : `^${process.env.FILTER.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}$` - ) + ) : /./; export function suite(fn: (config: Test, test_dir: string) => void) { diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/index.js b/sites/svelte-5-preview/src/lib/workers/bundler/index.js index f0a2bcd1f5e7..924f5becfca8 100644 --- a/sites/svelte-5-preview/src/lib/workers/bundler/index.js +++ b/sites/svelte-5-preview/src/lib/workers/bundler/index.js @@ -535,7 +535,7 @@ async function bundle({ uid, files }) { exports: 'named' // sourcemap: 'inline' }) - )?.output?.[0] + )?.output?.[0] : null; return { diff --git a/sites/svelte.dev/src/routes/auth/login/+server.js b/sites/svelte.dev/src/routes/auth/login/+server.js index 0f6295d3bd1f..b549ff4202b1 100644 --- a/sites/svelte.dev/src/routes/auth/login/+server.js +++ b/sites/svelte.dev/src/routes/auth/login/+server.js @@ -12,7 +12,7 @@ export const GET = client_id }).toString(); throw redirect(302, Location); - } + } : () => new Response( ` From e0104f05c5e86dc2811b656cd54b7b8cbc9b3ba6 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 14 Jan 2024 18:18:04 -0700 Subject: [PATCH 56/76] Revert "fix: It looks like a bunch of unformatted shit came in through main?? idk" This reverts commit ab851d5627168686395e37d7740b76485e98ce7e. --- packages/svelte/src/compiler/errors.js | 6 +-- packages/svelte/src/compiler/legacy.js | 2 +- .../compiler/phases/1-parse/state/element.js | 16 ++++---- .../phases/1-parse/utils/mapped_code.js | 4 +- .../src/compiler/phases/2-analyze/index.js | 12 +++--- .../3-transform/client/transform-client.js | 2 +- .../phases/3-transform/client/utils.js | 8 ++-- .../client/visitors/javascript-runes.js | 7 +++- .../3-transform/client/visitors/template.js | 40 +++++++++---------- .../3-transform/server/transform-server.js | 22 +++++----- .../src/compiler/phases/3-transform/utils.js | 2 +- .../svelte/src/compiler/preprocess/index.js | 2 +- .../svelte/src/compiler/utils/mapped_code.js | 4 +- packages/svelte/src/easing/index.js | 12 +++--- packages/svelte/src/internal/client/each.js | 8 ++-- packages/svelte/src/internal/client/render.js | 12 +++--- .../svelte/src/internal/client/runtime.js | 8 ++-- .../svelte/src/internal/client/transitions.js | 4 +- .../svelte/src/internal/client/validate.js | 4 +- packages/svelte/src/store/index.js | 2 +- packages/svelte/svelte-html.d.ts | 8 ++-- packages/svelte/tests/suite.ts | 2 +- .../src/lib/workers/bundler/index.js | 2 +- .../src/routes/auth/login/+server.js | 2 +- 24 files changed, 97 insertions(+), 94 deletions(-) diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index c083604b87c1..25a21d8402f8 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -234,8 +234,8 @@ const attributes = { type === 'no-each' ? `An element that uses the animate directive must be the immediate child of a keyed each block` : type === 'each-key' - ? `An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?` - : `An element that uses the animate directive must be the sole child of a keyed each block`, + ? `An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?` + : `An element that uses the animate directive must be the sole child of a keyed each block`, 'duplicate-animation': () => `An element can only have one 'animate' directive`, /** @param {string[] | undefined} [modifiers] */ 'invalid-event-modifier': (modifiers) => @@ -262,7 +262,7 @@ const attributes = { ? `An element can only have one '${directive1}' directive` : `An element cannot have both ${describe(directive1)} directive and ${describe( directive2 - )} directive`; + )} directive`; }, 'invalid-let-directive-placement': () => 'let directive at invalid position' }; diff --git a/packages/svelte/src/compiler/legacy.js b/packages/svelte/src/compiler/legacy.js index 8d67f654c2c6..e8595fd52f9e 100644 --- a/packages/svelte/src/compiler/legacy.js +++ b/packages/svelte/src/compiler/legacy.js @@ -108,7 +108,7 @@ export function convert(source, ast) { // @ts-ignore delete node.parent; } - }) + }) : undefined }; }, diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index 3c479ade83e1..5f2eda1f17b5 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -108,12 +108,12 @@ export default function tag(parser) { const type = meta_tags.has(name) ? meta_tags.get(name) : regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component' - ? 'Component' - : name === 'title' && parent_is_head(parser.stack) - ? 'TitleElement' - : name === 'slot' - ? 'SlotElement' - : 'RegularElement'; + ? 'Component' + : name === 'title' && parent_is_head(parser.stack) + ? 'TitleElement' + : name === 'slot' + ? 'SlotElement' + : 'RegularElement'; /** @type {import('#compiler').ElementLike} */ // @ts-expect-error TODO can't figure out this error @@ -131,7 +131,7 @@ export default function tag(parser) { has_spread: false }, parent: null - } + } : { type: /** @type {import('#compiler').ElementLike['type']} */ (type), start, @@ -140,7 +140,7 @@ export default function tag(parser) { attributes: [], fragment: create_fragment(true), parent: null - }; + }; parser.allow_whitespace(); diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js b/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js index 01be1b492f62..95f136effa72 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/mapped_code.js @@ -275,7 +275,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { sourcemap_list, () => null, true // skip optional field `sourcesContent` - ) + ) : remapping( // use loader interface sourcemap_list[0], // last map @@ -291,7 +291,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { } ), true - ); + ); if (!map.file) delete map.file; // skip optional field `file` diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 98c148a3380c..0c207f95080b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -710,12 +710,12 @@ const runes_scope_tweaker = { rune === '$state' ? 'state' : rune === '$state.frozen' - ? 'frozen_state' - : rune === '$derived' - ? 'derived' - : path.is_rest - ? 'rest_prop' - : 'prop'; + ? 'frozen_state' + : rune === '$derived' + ? 'derived' + : path.is_rest + ? 'rest_prop' + : 'prop'; } if (rune === '$props') { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 27069478c896..1dfafd64b45c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -201,7 +201,7 @@ export function client_component(source, analysis, options) { b.call('$.validate_store', store_reference, b.literal(name.slice(1))), store_get ]) - ) + ) : b.thunk(store_get) ) ); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index d8e600808625..7490636a218d 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -20,11 +20,11 @@ export function get_assignment_value(node, { state, visit }) { return operator === '=' ? /** @type {import('estree').Expression} */ (visit(node.right)) : // turn something like x += 1 into x = x + 1 - b.binary( + b.binary( /** @type {import('estree').BinaryOperator} */ (operator.slice(0, -1)), serialize_get_binding(node.left, state), /** @type {import('estree').Expression} */ (visit(node.right)) - ); + ); } else if ( node.left.type === 'MemberExpression' && node.left.object.type === 'ThisExpression' && @@ -35,11 +35,11 @@ export function get_assignment_value(node, { state, visit }) { return operator === '=' ? /** @type {import('estree').Expression} */ (visit(node.right)) : // turn something like x += 1 into x = x + 1 - b.binary( + b.binary( /** @type {import('estree').BinaryOperator} */ (operator.slice(0, -1)), /** @type {import('estree').Expression} */ (visit(node.left)), /** @type {import('estree').Expression} */ (visit(node.right)) - ); + ); } else { return /** @type {import('estree').Expression} */ (visit(node.right)); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js index 63c46d8c6464..0150c30293ad 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js @@ -87,8 +87,11 @@ export const javascript_visitors_runes = { field.kind === 'state' ? b.call('$.source', should_proxy_or_freeze(init) ? b.call('$.proxy', init) : init) : field.kind === 'frozen_state' - ? b.call('$.source', should_proxy_or_freeze(init) ? b.call('$.freeze', init) : init) - : b.call('$.derived', b.thunk(init)); + ? b.call( + '$.source', + should_proxy_or_freeze(init) ? b.call('$.freeze', init) : init + ) + : b.call('$.derived', b.thunk(init)); } else { // if no arguments, we know it's state as `$derived()` is a compile error value = b.call('$.source'); 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 f1902ee8f643..4c9115f09ac1 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 @@ -655,15 +655,15 @@ function serialize_element_special_value_attribute(element, node_id, attribute, // This ensures things stay in sync with the select binding // in case of updates to the option value or new values appearing b.call('$.selected', node_id) - ]) + ]) : needs_option_call - ? b.sequence([ - inner_assignment, - // This ensures a one-way street to the DOM in case it's - b.call('$.select_option', node_id, value) - ]) - : inner_assignment + ? b.sequence([ + inner_assignment, + // This ensures a one-way street to the DOM in case it's + b.call('$.select_option', node_id, value) + ]) + : inner_assignment ); if (is_reactive) { @@ -953,7 +953,7 @@ function serialize_inline_component(node, component_name, context) { : b.call( '$.spread_props', ...props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p)) - ); + ); /** @param {import('estree').Identifier} node_id */ let fn = (node_id) => b.call( @@ -2349,7 +2349,7 @@ export const template_visitors = { ? b.arrow( [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.fallback)) - ) + ) : b.literal(null); const key_function = node.key && ((each_type & EACH_ITEM_REACTIVE) !== 0 || context.state.options.dev) @@ -2360,7 +2360,7 @@ export const template_visitors = { b.return(/** @type {import('estree').Expression} */ (context.visit(node.key))) ) ) - ) + ) : b.literal(null); if (node.index && each_node_meta.contains_group_binding) { @@ -2422,7 +2422,7 @@ export const template_visitors = { ? b.arrow( [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.alternate)) - ) + ) : b.literal(null) ) ) @@ -2441,7 +2441,7 @@ export const template_visitors = { ? b.arrow( [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.pending)) - ) + ) : b.literal(null), node.then ? b.arrow( @@ -2449,10 +2449,10 @@ export const template_visitors = { ? [ b.id('$$anchor'), /** @type {import('estree').Pattern} */ (context.visit(node.value)) - ] + ] : [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.then)) - ) + ) : b.literal(null), node.catch ? b.arrow( @@ -2460,10 +2460,10 @@ export const template_visitors = { ? [ b.id('$$anchor'), /** @type {import('estree').Pattern} */ (context.visit(node.error)) - ] + ] : [b.id('$$anchor')], /** @type {import('estree').BlockStatement} */ (context.visit(node.catch)) - ) + ) : b.literal(null) ) ) @@ -2885,9 +2885,9 @@ export const template_visitors = { /** @type {import('estree').Expression} */ (node.expression).type === 'ObjectExpression' ? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine - b.object_pattern(node.expression.properties) + b.object_pattern(node.expression.properties) : // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine - b.array_pattern(node.expression.elements), + b.array_pattern(node.expression.elements), b.member(b.id('$$slotProps'), b.id(node.name)) ), b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node)))) @@ -2980,7 +2980,7 @@ export const template_visitors = { : b.arrow( [b.id('$$anchor')], b.block(create_block(node, 'fallback', node.fragment.nodes, context)) - ); + ); const expression = is_default ? b.member(b.id('$$props'), b.id('children')) 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 03f8f596cc12..f6fab25b9cfd 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 @@ -351,11 +351,11 @@ function get_assignment_value(node, { state, visit }) { return operator === '=' ? /** @type {import('estree').Expression} */ (visit(node.right)) : // turn something like x += 1 into x = x + 1 - b.binary( + b.binary( /** @type {import('estree').BinaryOperator} */ (operator.slice(0, -1)), serialize_get_binding(node.left, state), /** @type {import('estree').Expression} */ (visit(node.right)) - ); + ); } else { return /** @type {import('estree').Expression} */ (visit(node.right)); } @@ -780,7 +780,7 @@ function serialize_element_spread_attributes( b.id('join') ), b.literal(' ') - ) + ) : b.literal(''); args.push( b.object([ @@ -933,7 +933,7 @@ function serialize_inline_component(node, component_name, context) { : b.call( '$.spread_props', b.array(props_and_spreads.map((p) => (Array.isArray(p) ? b.object(p) : p))) - ); + ); /** @type {import('estree').Statement} */ let statement = b.stmt( @@ -942,7 +942,7 @@ function serialize_inline_component(node, component_name, context) { ? b.call( '$.validate_component', typeof component_name === 'string' ? b.id(component_name) : component_name - ) + ) : component_name, b.id('$$payload'), props_expression @@ -1034,7 +1034,7 @@ const javascript_visitors_legacy = { '$.value_or_fallback', prop, /** @type {import('estree').Expression} */ (visit(declarator.init)) - ) + ) : prop; declarations.push(b.declarator(declarator.id, init)); @@ -1172,7 +1172,7 @@ const template_visitors = { template: [], init: [] } - } + } : { ...context, state }; const { hoisted, trimmed } = clean_nodes( @@ -1496,9 +1496,9 @@ const template_visitors = { b.let( node.expression.type === 'ObjectExpression' ? // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine - b.object_pattern(node.expression.properties) + b.object_pattern(node.expression.properties) : // @ts-expect-error types don't match, but it can't contain spread elements and the structure is otherwise fine - b.array_pattern(node.expression.elements), + b.array_pattern(node.expression.elements), b.member(b.id('$$slotProps'), b.id(node.name)) ), b.return(b.object(bindings.map((binding) => b.init(binding.node.name, binding.node)))) @@ -1714,12 +1714,12 @@ function serialize_element_attributes(node, context) { ? b.call( b.member(attribute.expression, b.id('includes')), serialize_attribute_value(value_attribute.value, context) - ) + ) : b.binary( '===', attribute.expression, serialize_attribute_value(value_attribute.value, context) - ), + ), metadata: { contains_call_expression: false, dynamic: false diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index 8ce6c0fd1ba0..a924a59e1a4b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -198,7 +198,7 @@ export function infer_namespace(namespace, parent, nodes, path) { const parent_node = parent.type === 'Fragment' ? // Messy: We know that Fragment calls create_block directly, so we can do this here - path.at(-1) + path.at(-1) : parent; if ( diff --git a/packages/svelte/src/compiler/preprocess/index.js b/packages/svelte/src/compiler/preprocess/index.js index d12c87076083..ad1061c78847 100644 --- a/packages/svelte/src/compiler/preprocess/index.js +++ b/packages/svelte/src/compiler/preprocess/index.js @@ -318,7 +318,7 @@ async function process_markup(process, source) { string: processed.code, map: processed.map ? // TODO: can we use decode_sourcemap? - typeof processed.map === 'string' + typeof processed.map === 'string' ? JSON.parse(processed.map) : processed.map : undefined, diff --git a/packages/svelte/src/compiler/utils/mapped_code.js b/packages/svelte/src/compiler/utils/mapped_code.js index aebeaa1c5131..a438e48fd710 100644 --- a/packages/svelte/src/compiler/utils/mapped_code.js +++ b/packages/svelte/src/compiler/utils/mapped_code.js @@ -258,7 +258,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { sourcemap_list, () => null, true // skip optional field `sourcesContent` - ) + ) : remapping( // use loader interface sourcemap_list[0], // last map @@ -271,7 +271,7 @@ export function combine_sourcemaps(filename, sourcemap_list) { } }, true - ); + ); if (!map.file) delete map.file; // skip optional field `file` // When source maps are combined and the leading map is empty, sources is not set. // Add the filename to the empty array in this case. diff --git a/packages/svelte/src/easing/index.js b/packages/svelte/src/easing/index.js index ed752d80f23e..1c9ced01d6bc 100644 --- a/packages/svelte/src/easing/index.js +++ b/packages/svelte/src/easing/index.js @@ -59,10 +59,10 @@ export function bounceOut(t) { return t < a ? 7.5625 * t2 : t < b - ? 9.075 * t2 - 9.9 * t + 3.4 - : t < c - ? ca * t2 - cb * t + cc - : 10.8 * t * t - 20.52 * t + 10.72; + ? 9.075 * t2 - 9.9 * t + 3.4 + : t < c + ? ca * t2 - cb * t + cc + : 10.8 * t * t - 20.52 * t + 10.72; } /** @@ -180,8 +180,8 @@ export function expoInOut(t) { return t === 0.0 || t === 1.0 ? t : t < 0.5 - ? +0.5 * Math.pow(2.0, 20.0 * t - 10.0) - : -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0; + ? +0.5 * Math.pow(2.0, 20.0 * t - 10.0) + : -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0; } /** diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js index e66574fba0e7..4c0e5dec7e91 100644 --- a/packages/svelte/src/internal/client/each.js +++ b/packages/svelte/src/internal/client/each.js @@ -133,8 +133,8 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re array = is_array(maybe_array) ? maybe_array : maybe_array == null - ? [] - : Array.from(maybe_array); + ? [] + : Array.from(maybe_array); if (key_fn !== null) { keys = array.map(key_fn); } else if ((flags & EACH_KEYED) === 0) { @@ -777,8 +777,8 @@ function each_item_block(item, key, index, render_fn, flags) { const item_value = each_item_not_reactive ? item : (flags & EACH_IS_IMMUTABLE) === 0 - ? mutable_source(item) - : source(item); + ? mutable_source(item) + : source(item); const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); const block = create_each_item_block(item_value, index_value, key); diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 16ab59cf6566..c466fddab3c7 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -881,8 +881,8 @@ export function bind_resize_observer(dom, type, update) { type === 'contentRect' || type === 'contentBoxSize' ? resize_observer_content_box : type === 'borderBoxSize' - ? resize_observer_border_box - : resize_observer_device_pixel_content_box; + ? resize_observer_border_box + : resize_observer_device_pixel_content_box; const unsub = observer.observe(dom, /** @param {any} entry */ (entry) => update(entry[type])); render_effect(() => unsub); } @@ -1606,8 +1606,8 @@ export function element(anchor_node, tag_fn, render_fn, is_svg = false) { ? current_hydration_fragment !== null ? /** @type {HTMLElement | SVGElement} */ (current_hydration_fragment[0]) : is_svg - ? document.createElementNS('http://www.w3.org/2000/svg', tag) - : document.createElement(tag) + ? document.createElementNS('http://www.w3.org/2000/svg', tag) + : document.createElement(tag) : null; const prev_element = element; if (prev_element !== null) { @@ -2869,7 +2869,7 @@ export function mount(component, options) { PassiveDelegatedEvents.includes(event_name) ? { passive: true - } + } : undefined ); // The document listener ensures we catch events that originate from elements that were @@ -2880,7 +2880,7 @@ export function mount(component, options) { PassiveDelegatedEvents.includes(event_name) ? { passive: true - } + } : undefined ); } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 6ed46a753119..b4ef2f3e7fe3 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -561,7 +561,7 @@ function infinite_loop_guard() { 'ERR_SVELTE_TOO_MANY_UPDATES' + (DEV ? ': Maximum update depth exceeded. This can happen when a reactive block or effect ' + - 'repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops.' + 'repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops.' : '') ); } @@ -1181,8 +1181,8 @@ export function set_signal_value(signal, value) { 'ERR_SVELTE_UNSAFE_MUTATION' + (DEV ? ": Unsafe mutations during Svelte's render or derived phase are not permitted in runes mode. " + - 'This can lead to unexpected errors and possibly cause infinite loops.\n\nIf this mutation is not meant ' + - 'to be reactive do not use the "$state" rune for that declaration.' + 'This can lead to unexpected errors and possibly cause infinite loops.\n\nIf this mutation is not meant ' + + 'to be reactive do not use the "$state" rune for that declaration.' : '') ); } @@ -1696,7 +1696,7 @@ export function safe_not_equal(a, b) { // eslint-disable-next-line eqeqeq return a != a ? // eslint-disable-next-line eqeqeq - b == b + b == b : a !== b || (a !== null && typeof a === 'object') || typeof a === 'function'; } diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 9a368d054bb5..96ad2fd35a32 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -542,10 +542,10 @@ export function bind_transition(dom, get_transition_fn, props_fn, direction, glo { from: /** @type {DOMRect} */ (from), to: dom.getBoundingClientRect() }, props, {} - ) + ) : /** @type {import('./types.js').TransitionFn} */ (transition_fn)(dom, props, { direction - }); + }); }); transition = create_transition(dom, init, direction, transition_effect); diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index e3316891ebe3..c6f590a420ac 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -75,8 +75,8 @@ export function validate_each_keys(collection, key_fn) { const array = is_array(maybe_array) ? maybe_array : maybe_array == null - ? [] - : Array.from(maybe_array); + ? [] + : Array.from(maybe_array); const length = array.length; for (let i = 0; i < length; i++) { const key = key_fn(array[i], i); diff --git a/packages/svelte/src/store/index.js b/packages/svelte/src/store/index.js index 857eab0af8e1..e18f7df8b231 100644 --- a/packages/svelte/src/store/index.js +++ b/packages/svelte/src/store/index.js @@ -33,7 +33,7 @@ export function safe_not_equal(a, b) { // eslint-disable-next-line eqeqeq return a != a ? // eslint-disable-next-line eqeqeq - b == b + b == b : a !== b || (a && typeof a === 'object') || typeof a === 'function'; } diff --git a/packages/svelte/svelte-html.d.ts b/packages/svelte/svelte-html.d.ts index 3547ad0d33b2..614d1fb3bcc9 100644 --- a/packages/svelte/svelte-html.d.ts +++ b/packages/svelte/svelte-html.d.ts @@ -37,8 +37,8 @@ declare global { ): Key extends keyof ElementTagNameMap ? ElementTagNameMap[Key] : Key extends keyof SVGElementTagNameMap - ? SVGElementTagNameMap[Key] - : any; + ? SVGElementTagNameMap[Key] + : any; function createElement( // "undefined | null" because of element: Key | undefined | null, @@ -47,8 +47,8 @@ declare global { ): Key extends keyof ElementTagNameMap ? ElementTagNameMap[Key] : Key extends keyof SVGElementTagNameMap - ? SVGElementTagNameMap[Key] - : any; + ? SVGElementTagNameMap[Key] + : any; // For backwards-compatibility and ease-of-use, in case someone enhanced the typings from import('svelte/elements').HTMLAttributes/SVGAttributes // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/svelte/tests/suite.ts b/packages/svelte/tests/suite.ts index 34609d5e4aaf..b694e4cd9578 100644 --- a/packages/svelte/tests/suite.ts +++ b/packages/svelte/tests/suite.ts @@ -17,7 +17,7 @@ const filter = process.env.FILTER process.env.FILTER.startsWith('/') ? process.env.FILTER.slice(1, -1).replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') : `^${process.env.FILTER.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}$` - ) + ) : /./; export function suite(fn: (config: Test, test_dir: string) => void) { diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/index.js b/sites/svelte-5-preview/src/lib/workers/bundler/index.js index 924f5becfca8..f0a2bcd1f5e7 100644 --- a/sites/svelte-5-preview/src/lib/workers/bundler/index.js +++ b/sites/svelte-5-preview/src/lib/workers/bundler/index.js @@ -535,7 +535,7 @@ async function bundle({ uid, files }) { exports: 'named' // sourcemap: 'inline' }) - )?.output?.[0] + )?.output?.[0] : null; return { diff --git a/sites/svelte.dev/src/routes/auth/login/+server.js b/sites/svelte.dev/src/routes/auth/login/+server.js index b549ff4202b1..0f6295d3bd1f 100644 --- a/sites/svelte.dev/src/routes/auth/login/+server.js +++ b/sites/svelte.dev/src/routes/auth/login/+server.js @@ -12,7 +12,7 @@ export const GET = client_id }).toString(); throw redirect(302, Location); - } + } : () => new Response( ` From 5104475fd449f516f7f845586736dcf86ad6d1c2 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 14 Jan 2024 18:20:50 -0700 Subject: [PATCH 57/76] fix: format again --- packages/svelte/src/main/private.d.ts | 4 ++-- packages/svelte/src/main/public.d.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/main/private.d.ts b/packages/svelte/src/main/private.d.ts index a56022329778..00bca84aed4c 100644 --- a/packages/svelte/src/main/private.d.ts +++ b/packages/svelte/src/main/private.d.ts @@ -12,5 +12,5 @@ export type PropsWithChildren = Props & (Props extends { children?: any } ? {} : Slots extends { default: any } - ? { children?: Snippet<[]> } - : {}); + ? { children?: Snippet<[]> } + : {}); diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts index 17f6d3b2a5d4..10c3e798de9e 100644 --- a/packages/svelte/src/main/public.d.ts +++ b/packages/svelte/src/main/public.d.ts @@ -200,7 +200,7 @@ export type Snippet = ): typeof SnippetReturn & { _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; }; - }; + }; interface DispatchOptions { cancelable?: boolean; @@ -214,8 +214,8 @@ export interface EventDispatcher> { ...args: null extends EventMap[Type] ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions] : undefined extends EventMap[Type] - ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions] - : [type: Type, parameter: EventMap[Type], options?: DispatchOptions] + ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions] + : [type: Type, parameter: EventMap[Type], options?: DispatchOptions] ): boolean; } From 80b52a70e0e4fd03b144bcd6e60821666db5ccf8 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Sun, 14 Jan 2024 18:26:55 -0700 Subject: [PATCH 58/76] this is getting ridiculous --- packages/svelte/types/index.d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 5cec96541eba..beca873448e2 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -201,7 +201,7 @@ declare module 'svelte' { ): typeof SnippetReturn & { _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; }; - }; + }; interface DispatchOptions { cancelable?: boolean; @@ -319,8 +319,8 @@ declare module 'svelte' { (Props extends { children?: any } ? {} : Slots extends { default: any } - ? { children?: Snippet<[]> } - : {}); + ? { children?: Snippet<[]> } + : {}); /** * Mounts the given component to the given target and returns a handle to the component's public accessors * as well as a `$set` and `$destroy` method to update the props of the component or destroy it. @@ -1848,7 +1848,7 @@ declare module 'svelte/legacy' { ): typeof SnippetReturn & { _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; }; - }; + }; // Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already // if you're curious why this is here and not declared as an unexported type from `public.d.ts`, try putting it there // and see what happens in the output `index.d.ts` -- it breaks, presumably because of a TypeScript compiler bug. @@ -1856,8 +1856,8 @@ declare module 'svelte/legacy' { (Props extends { children?: any } ? {} : Slots extends { default: any } - ? { children?: Snippet<[]> } - : {}); + ? { children?: Snippet<[]> } + : {}); } declare module 'svelte/motion' { From d9803f141016a2087198914bcbdee06d23b25d84 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Thu, 25 Jan 2024 18:58:05 -0700 Subject: [PATCH 59/76] Update tag.js Co-authored-by: Rich Harris --- packages/svelte/src/compiler/phases/1-parse/state/tag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 687262576c07..aa1bfad955c6 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -269,7 +269,7 @@ function open(parser) { const snippet_declaration_end = find_matching_bracket(parser.template, parser.index, '{'); if (!snippet_declaration_end) { - error(start, 'TODO', 'Expected a closing curly bracket'); + error(parser.template.length, 'unexpected-eof'); } // we'll eat this later, so save it From a74e2c5a883fba528e446f60cf279da17478dad8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Jan 2024 15:19:38 -0500 Subject: [PATCH 60/76] fix errors --- packages/svelte/src/compiler/errors.js | 4 +++- .../svelte/src/compiler/phases/1-parse/state/tag.js | 11 ++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 98741e29f40d..84228d9e0744 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -90,7 +90,9 @@ const parse = { 'duplicate-script-element': () => `A component can have a single top-level - -{#snippet foo(n, doubled, { tripled }, quadrupled, whatever)} -

clicks: {n.value}, doubled: {doubled.value}, tripled: {tripled.value}, quadrupled: {quadrupled.value}, something else: {whatever.value}

-{/snippet} - -{@render foo(...[count, doubled, {tripled}, quadrupled, whatever_comes_after_that])} - - From 569bc1fe5cd2ac396fc7caee4a6a62a8ba02b661 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Jan 2024 18:42:11 -0500 Subject: [PATCH 68/76] tidy up SnippetBlock interface --- packages/svelte/src/compiler/legacy.js | 2 +- packages/svelte/src/compiler/phases/1-parse/state/tag.js | 9 +++------ .../phases/3-transform/client/visitors/template.js | 4 ++-- .../phases/3-transform/server/transform-server.js | 2 +- packages/svelte/src/compiler/phases/scope.js | 6 ++++-- packages/svelte/src/compiler/types/template.d.ts | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/svelte/src/compiler/legacy.js b/packages/svelte/src/compiler/legacy.js index e8595fd52f9e..363742e7b1ac 100644 --- a/packages/svelte/src/compiler/legacy.js +++ b/packages/svelte/src/compiler/legacy.js @@ -356,7 +356,7 @@ export function convert(source, ast) { start: node.start, end: node.end, expression: node.expression, - context: node.context, + parameters: node.parameters, children: node.body.nodes.map((child) => visit(child)) }; }, 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 558abec1484f..1a62c57c46d7 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,7 @@ function open(parser) { parser.allow_whitespace(); /** @type {import('estree').Pattern[]} */ - const elements = []; + const parameters = []; while (!parser.match(')')) { let pattern = read_context(parser); @@ -289,7 +289,7 @@ function open(parser) { }; } - elements.push(pattern); + parameters.push(pattern); if (!parser.eat(',')) break; parser.allow_whitespace(); @@ -312,10 +312,7 @@ function open(parser) { end: name_end, name }, - context: { - type: 'ArrayPattern', - elements - }, + parameters, body: create_fragment() }) ); 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 276c1e3c2ca7..0e52ee609ed1 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 @@ -2475,8 +2475,8 @@ export const template_visitors = { /** @type {import('estree').Statement[]} */ const declarations = []; - for (let i = 0; i < node.context.elements.length; i++) { - const argument = node.context.elements[i]; + for (let i = 0; i < node.parameters.length; i++) { + const argument = node.parameters[i]; if (!argument) continue; 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 80335a074f70..2cce11b69fe9 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 @@ -1446,7 +1446,7 @@ const template_visitors = { // TODO hoist where possible /** @type {import('estree').Pattern[]} */ const args = [b.id('$$payload')]; - for (const arg of node.context.elements) { + for (const arg of node.parameters) { if (arg) args.push(arg); } diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 02f111697d46..79fd249adf1e 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -592,8 +592,10 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { const child_scope = state.scope.child(); scopes.set(node, child_scope); - for (const id of extract_identifiers(node.context)) { - child_scope.declare(id, 'each', 'let'); + for (const param of node.parameters) { + for (const id of extract_identifiers(param)) { + 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 71a4e1d83542..7bb299df7bfc 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -418,7 +418,7 @@ export interface KeyBlock extends BaseNode { export interface SnippetBlock extends BaseNode { type: 'SnippetBlock'; expression: Identifier; - context: ArrayPattern; + parameters: Pattern[]; body: Fragment; } From e55d8d100a47c8cdccd3e137b4c9b1c38f6a612c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Jan 2024 19:04:13 -0500 Subject: [PATCH 69/76] fix test --- .../samples/snippets/output.json | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/packages/svelte/tests/parser-modern/samples/snippets/output.json b/packages/svelte/tests/parser-modern/samples/snippets/output.json index 1444369725ce..732ba5888aa9 100644 --- a/packages/svelte/tests/parser-modern/samples/snippets/output.json +++ b/packages/svelte/tests/parser-modern/samples/snippets/output.json @@ -24,37 +24,34 @@ "end": 42, "name": "foo" }, - "context": { - "type": "ArrayPattern", - "elements": [ - { - "type": "Identifier", - "start": 43, + "parameters": [ + { + "type": "Identifier", + "start": 43, + "end": 54, + "name": "msg", + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 46, "end": 54, - "name": "msg", "typeAnnotation": { - "type": "TSTypeAnnotation", - "start": 46, + "type": "TSStringKeyword", + "start": 48, "end": 54, - "typeAnnotation": { - "type": "TSStringKeyword", - "start": 48, - "end": 54, - "loc": { - "start": { - "line": 3, - "column": 19 - }, - "end": { - "line": 3, - "column": 25 - } + "loc": { + "start": { + "line": 3, + "column": 19 + }, + "end": { + "line": 3, + "column": 25 } } } } - ] - }, + } + ], "body": { "type": "Fragment", "nodes": [ From 1390466d864e442a104fc4e30eea7d986e6be8f8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Jan 2024 19:05:49 -0500 Subject: [PATCH 70/76] simplify --- .../3-transform/client/visitors/template.js | 13 +++++++------ packages/svelte/src/internal/client/runtime.js | 18 +++--------------- packages/svelte/src/internal/common.js | 2 +- packages/svelte/src/internal/index.js | 2 +- packages/svelte/src/internal/server/index.js | 6 +----- packages/svelte/src/store/index.js | 5 +---- packages/svelte/src/store/utils.js | 4 ++-- 7 files changed, 16 insertions(+), 34 deletions(-) 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 0e52ee609ed1..e196a0937063 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 @@ -2481,14 +2481,15 @@ export const template_visitors = { if (!argument) continue; if (argument.type === 'Identifier') { - args.push(argument); + args.push({ + type: 'AssignmentPattern', + left: argument, + right: b.id('$.noop') + }); const binding = /** @type {import('#compiler').Binding} */ ( context.state.scope.get(argument.name) ); - // we can't use `b.maybe_call` because it can result in invalid javascript if - // this expression appears on the left side of an assignment somewhere. For example: - // `$.maybe_call(myArg).value = 1` is valid JavaScript, but `$.myArg?.().value = 1` is not - binding.expression = b.call('$.maybe_call', argument); + binding.expression = b.call(argument); continue; } @@ -2505,7 +2506,7 @@ export const template_visitors = { path.node, b.thunk( /** @type {import('estree').Expression} */ ( - context.visit(path.expression?.(b.call('$.maybe_call', b.id(arg_alias)))) + context.visit(path.expression?.(b.maybe_call(b.id(arg_alias)))) ) ) ) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 388bff2a36b2..92e10cb54786 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1,6 +1,6 @@ import { DEV } from 'esm-env'; import { subscribe_to_store } from '../../store/utils.js'; -import { EMPTY_FUNC, run_all } from '../common.js'; +import { noop, run_all } from '../common.js'; import { array_prototype, get_descriptor, @@ -860,7 +860,7 @@ export function store_get(store, store_name, stores) { store: null, last_value: null, value: mutable_source(UNINITIALIZED), - unsubscribe: EMPTY_FUNC + unsubscribe: noop }; // TODO: can we remove this code? it was refactored out when we split up source/comptued signals // push_destroy_fn(entry.value, () => { @@ -890,7 +890,7 @@ export function store_get(store, store_name, stores) { function connect_store_to_signal(store, source) { if (store == null) { set(source, undefined); - return EMPTY_FUNC; + return noop; } /** @param {V} v */ @@ -2109,18 +2109,6 @@ if (DEV) { throw_rune_error('$props'); } -/** - * @template {Function | undefined} T - * @param {T} fn - * @returns {ReturnType | undefined} - */ -export function maybe_call(fn) { - if (fn === undefined) { - return undefined; - } - return fn(); -} - /** * Expects a value that was wrapped with `freeze` and makes it frozen. * @template T diff --git a/packages/svelte/src/internal/common.js b/packages/svelte/src/internal/common.js index 1ea482a8013c..2a4295f2761c 100644 --- a/packages/svelte/src/internal/common.js +++ b/packages/svelte/src/internal/common.js @@ -1,5 +1,5 @@ // eslint-disable-next-line @typescript-eslint/no-empty-function -export const EMPTY_FUNC = () => {}; +export const noop = () => {}; // Adapted from https://github.com/then/is-promise/blob/master/index.js // Distributed under MIT License https://github.com/then/is-promise/blob/master/LICENSE diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 207c6b43f0fc..ae1748b4287c 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -38,7 +38,6 @@ export { user_root_effect, inspect, unwrap, - maybe_call, freeze } from './client/runtime.js'; export * from './client/each.js'; @@ -54,3 +53,4 @@ export { $window as window, $document as document } from './client/operations.js'; +export { noop } from './common.js'; diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index cd152c932242..aafbd8252a23 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -1,6 +1,6 @@ import * as $ from '../client/runtime.js'; import { set_is_ssr } from '../client/runtime.js'; -import { is_promise } from '../common.js'; +import { is_promise, noop } from '../common.js'; import { subscribe_to_store } from '../../store/utils.js'; import { DOMBooleanAttributes } from '../../constants.js'; @@ -523,10 +523,6 @@ export function bind_props(props_parent, props_now) { } } -function noop() { - // noop -} - /** * @template V * @param {Promise} promise diff --git a/packages/svelte/src/store/index.js b/packages/svelte/src/store/index.js index 728eec3d497e..85954fc66c17 100644 --- a/packages/svelte/src/store/index.js +++ b/packages/svelte/src/store/index.js @@ -1,3 +1,4 @@ +import { noop } from '../internal/common.js'; import { subscribe_to_store } from './utils.js'; /** @@ -5,10 +6,6 @@ import { subscribe_to_store } from './utils.js'; */ const subscriber_queue = []; -/** @returns {void} */ -// eslint-disable-next-line @typescript-eslint/no-empty-function -function noop() {} - /** * Creates a `Readable` store that allows reading by subscription. * diff --git a/packages/svelte/src/store/utils.js b/packages/svelte/src/store/utils.js index 979a2518d8f0..9bcc914fd138 100644 --- a/packages/svelte/src/store/utils.js +++ b/packages/svelte/src/store/utils.js @@ -1,4 +1,4 @@ -import { EMPTY_FUNC } from '../internal/common.js'; +import { noop } from '../internal/common.js'; /** * @template T @@ -15,7 +15,7 @@ export function subscribe_to_store(store, run, invalidate) { // @ts-expect-error if (invalidate) invalidate(undefined); - return EMPTY_FUNC; + return noop; } // Svelte store takes a private second argument From 9f25d8c4285ba3cee5e4067e68c6a8c94db707ec Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Jan 2024 19:12:11 -0500 Subject: [PATCH 71/76] tweak --- .../phases/3-transform/server/transform-server.js | 9 ++------- packages/svelte/src/compiler/types/template.d.ts | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) 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 2cce11b69fe9..b6e53a739b6e 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 @@ -1444,19 +1444,14 @@ const template_visitors = { }, SnippetBlock(node, context) { // TODO hoist where possible - /** @type {import('estree').Pattern[]} */ - const args = [b.id('$$payload')]; - for (const arg of node.parameters) { - if (arg) args.push(arg); - } - context.state.init.push( b.function_declaration( node.expression, - args, + [b.id('$$payload'), ...node.parameters], /** @type {import('estree').BlockStatement} */ (context.visit(node.body)) ) ); + if (context.state.options.dev) { context.state.init.push(b.stmt(b.call('$.add_snippet_symbol', node.expression))); } diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 7bb299df7bfc..d9c6f5102063 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -148,7 +148,7 @@ export interface DebugTag extends BaseNode { export interface RenderTag extends BaseNode { type: 'RenderTag'; expression: Identifier; - arguments: (Expression | SpreadElement)[]; + arguments: Array; } type Tag = ExpressionTag | HtmlTag | ConstTag | DebugTag | RenderTag; From d258b829832bd8e3605758c12469de44d5d17b3e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Jan 2024 20:38:42 -0500 Subject: [PATCH 72/76] revert example, so that it matches the surrounding text --- .../src/routes/docs/content/01-api/03-snippets.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md index 66bbb53faad5..3a7ee6770d18 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md @@ -58,7 +58,7 @@ Snippets, and _render tags_, are a way to create reusable chunks of markup insid {/each} ``` -A snippet behaves pretty much like a regular function declaration: It can have multiple parameters, those parameters can be destructured, and they can have default values. However, you cannot use `...rest` params. ([demo](/#H4sIAAAAAAAAE2WO0YrCQAxFfyXGhVoo9L3Wot9hF6xt1IHpTJikggzz78tI2YX1MTecc2_Em7Ek2JwjumEmbPDEjBXqi_MhT7JKWKH4JYw5aWUMhrXrXa-WFCLMJDLcCQ5wMVoIOK8gHu6BBgX1IETw8ssFEhzgi4Nn2ZX73rX1n8rFrTjDTAoPstbv8pjqQ_3hLFPe0XL3piBmLG0grmDatDV3vYv1ak_vrmMgN1FYq4rBmpGKrPr_ufpr8buiTFjh7CdzMzRho2Gh9J1-AFhYxBNCAQAA)): +Snippet parameters can be destructured ([demo](/#H4sIAAAAAAAAE5VTYW-bMBD9KyeiKYlEY4jWfSAk2n5H6QcXDmwVbMs2SzuL_z6DTRqp2rQJ2Ycfd_ced2eXtLxHkxRPLhF0wKRIfiiVpIl9V_PB_MTeoj8bOep6RkpTa67spRKV7dECH2iHBs7wNCOVdcFU1ui6gC2zVpmCEMVrMw4HxaSVhnzLMnLMsm26Ol95Y1kBHr9BDHnHbAHHO6ymynIpfF7LuAncwKgBCj0Xrx_5mMb2jh3f6KB6PNRy2AaXKf1fuY__KPfxj3KlQGikL5aQdpUxm-dTJUryUVdRsvwSqEviX2fIbYzgSvmCt7wbNe4ceMUpRIoUFkkpBBkw7ZfMZXC-BLKSDx3Q3p5djJrA-SR-X4K9DdHT6u-jo-flFlKSO3ThIDcSR6LIKUhGWrN1QGhs16LLbXgbjoe5U1PkozCfzu7uy2WtpfuuUTSo1_9ffPZrJKGLoyuwNxjBv0Q4wmdSR2aFi9jS2Pc-FIrlEKeilcI-GP4LfVtxOM1gyO1XSLp6vtD6tdNyFE0BV8YtngKuaNNw0RWQx_jKDlR33M9E5h-PQhZxfxEt6gIaLdWDYbSR191RvcFXv_LMb7p7obssXZ5Dvt_f9HgzdzZKibOZZ9mXmHkdTTpaefqsd4OIay4_hksd_I0fZMNbjk1SWD3i9Dz9BpdEPu8sBAAA)): ```svelte {#snippet figure({ src, caption, width, height })} @@ -69,6 +69,8 @@ A snippet behaves pretty much like a regular function declaration: It can have m {/snippet} ``` +Like function declarations, snippets can have an arbitrary number of parameters, which can have default values. You cannot use rest parameters however. + ## Snippet scope Snippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the ` ``` -The `Snippet` type is generic. Here's how you'd type various cases: +With this change, red squigglies will appear if you try and use the component without providing a `data` prop and a `row` snippet. Notice that the type argument provided to `Snippet` is a tuple, since snippets can have multiple parameters. -```ts -// @errors: 2305 -import type { Snippet } from 'svelte'; +We can tighten things up further by declaring a generic, so that `data` and `row` refer to the same type: -type SnippetWithNoArgs = Snippet; -type SnippetWithOneArg = Snippet<[argOne: number]>; -type SnippetWithMultipleArgs = Snippet< - [argOne: number, argTwo: string] ->; -``` +```diff +- +``` -```svelte -{#snippet withNoArgs()} - -{/snippet} +## Snippets and slots -{#snippet withOneArg(argOne: number)} - -{/snippet} +In Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/special-elements#slot). Snippets are more powerful and flexible, and as such slots are deprecated in Svelte 5. -{#snippet withMultipleArgs(argOne: number, argTwo: string)} - -{/snippet} -``` +They continue to work, however, and you can mix and match snippets and slots in your components. From 069ad1dd7e36e928d70663d057708b5dd5527eed Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Jan 2024 21:16:12 -0500 Subject: [PATCH 75/76] temporarily revert const parsing changes, to get prettier working again (???) --- .../src/compiler/phases/1-parse/state/tag.js | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) 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 1a62c57c46d7..9f8a57c0556c 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -3,6 +3,7 @@ import read_expression from '../read/expression.js'; import { error } from '../../../errors.js'; import { create_fragment } from '../utils/create.js'; import { walk } from 'zimmerframe'; +import { parse } from '../acorn.js'; const regex_whitespace_with_closing_curly_brace = /^\s*}/; @@ -549,17 +550,42 @@ function special(parser) { } if (parser.eat('const')) { - parser.allow_whitespace(); + // {@const a = b} + const start_index = parser.index - 5; + parser.require_whitespace(); - const id = read_context(parser); - parser.allow_whitespace(); + let end_index = parser.index; + /** @type {import('estree').VariableDeclaration | undefined} */ + let declaration = undefined; - parser.eat('=', true); - parser.allow_whitespace(); + // Can't use parse_expression_at here, so we try to parse until we find the correct range + const dummy_spaces = parser.template.substring(0, start_index).replace(/[^\n]/g, ' '); + while (true) { + end_index = parser.template.indexOf('}', end_index + 1); + if (end_index === -1) break; + try { + const node = parse( + dummy_spaces + parser.template.substring(start_index, end_index), + parser.ts + ).body[0]; + if (node?.type === 'VariableDeclaration') { + declaration = node; + break; + } + } catch (e) { + continue; + } + } - const init = read_expression(parser); - parser.allow_whitespace(); + if ( + declaration === undefined || + declaration.declarations.length !== 1 || + declaration.declarations[0].init === undefined + ) { + error(start, 'invalid-const'); + } + parser.index = end_index; parser.eat('}', true); parser.append( @@ -567,11 +593,7 @@ function special(parser) { type: 'ConstTag', start, end: parser.index, - declaration: { - type: 'VariableDeclaration', - kind: 'const', - declarations: [{ type: 'VariableDeclarator', id, init }] - } + declaration }) ); } From 84c6eda9d3fbbfa86f405030d637de40c9e8b5fe Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 30 Jan 2024 21:19:52 -0500 Subject: [PATCH 76/76] oops --- packages/svelte/src/main/public.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts index 76576af0fd2b..10e8e11a9d76 100644 --- a/packages/svelte/src/main/public.d.ts +++ b/packages/svelte/src/main/public.d.ts @@ -224,4 +224,3 @@ export interface EventDispatcher> { export * from './main-client.js'; import './ambient.js'; -import type { PropsWithChildren } from './private.js';