From 2c877e0bd4b0ca99b05a61d7ad5ec8d51a555d48 Mon Sep 17 00:00:00 2001 From: Tim Londschien Date: Wed, 22 Oct 2025 20:46:20 +0200 Subject: [PATCH] Initial {#switch ...} support --- .../src/compiler/phases/1-parse/state/tag.js | 85 ++++++++++ .../phases/2-analyze/css/css-prune.js | 9 +- .../src/compiler/phases/2-analyze/index.js | 3 + .../phases/2-analyze/visitors/ConstTag.js | 1 + .../2-analyze/visitors/RegularElement.js | 1 + .../phases/2-analyze/visitors/SvelteSelf.js | 1 + .../phases/2-analyze/visitors/SwitchBlock.js | 34 ++++ .../3-transform/client/transform-client.js | 2 + .../client/visitors/BindDirective.js | 1 + .../client/visitors/SwitchBlock.js | 42 +++++ .../3-transform/server/transform-server.js | 2 + .../server/visitors/SwitchBlock.js | 37 +++++ .../src/compiler/phases/3-transform/utils.js | 1 + .../svelte/src/compiler/types/template.d.ts | 9 + .../svelte/src/compiler/utils/builders.js | 29 ++++ .../src/internal/client/dom/blocks/switch.js | 66 ++++++++ .../src/internal/client/dom/hydration.js | 2 +- packages/svelte/src/internal/client/index.js | 1 + .../samples/switch-block-default/input.svelte | 3 + .../samples/switch-block-default/output.json | 93 +++++++++++ .../samples/switch-block-empty/input.svelte | 1 + .../samples/switch-block-empty/output.json | 49 ++++++ .../input.svelte | 7 + .../output.json | 154 ++++++++++++++++++ .../switch-block-multiple-case/input.svelte | 6 + .../switch-block-multiple-case/output.json | 148 +++++++++++++++++ .../switch-block-dependencies/_config.js | 19 +++ .../switch-block-dependencies/main.svelte | 22 +++ .../switch-dependency-order-2/Seo.svelte | 9 + .../switch-dependency-order-2/_config.js | 22 +++ .../switch-dependency-order-2/main.svelte | 17 ++ .../switch-dependency-order/_config.js | 16 ++ .../switch-dependency-order/main.svelte | 13 ++ .../switch-transition-inert/_config.js | 16 ++ .../switch-transition-inert/main.svelte | 16 ++ .../switch-transition-undefined/_config.js | 40 +++++ .../switch-transition-undefined/main.svelte | 16 ++ .../switch-block-case-match/_config.js | 7 + .../switch-block-case-match/_expected.html | 1 + .../switch-block-case-match/main.svelte | 7 + .../switch-block-case-no-match/_config.js | 7 + .../switch-block-case-no-match/_expected.html | 0 .../switch-block-case-no-match/main.svelte | 7 + .../samples/switch-block-default/_config.js | 7 + .../switch-block-default/_expected.html | 1 + .../samples/switch-block-default/main.svelte | 7 + packages/svelte/types/index.d.ts | 9 + 47 files changed, 1043 insertions(+), 3 deletions(-) create mode 100644 packages/svelte/src/compiler/phases/2-analyze/visitors/SwitchBlock.js create mode 100644 packages/svelte/src/compiler/phases/3-transform/client/visitors/SwitchBlock.js create mode 100644 packages/svelte/src/compiler/phases/3-transform/server/visitors/SwitchBlock.js create mode 100644 packages/svelte/src/internal/client/dom/blocks/switch.js create mode 100644 packages/svelte/tests/parser-modern/samples/switch-block-default/input.svelte create mode 100644 packages/svelte/tests/parser-modern/samples/switch-block-default/output.json create mode 100644 packages/svelte/tests/parser-modern/samples/switch-block-empty/input.svelte create mode 100644 packages/svelte/tests/parser-modern/samples/switch-block-empty/output.json create mode 100644 packages/svelte/tests/parser-modern/samples/switch-block-inside-switch-block/input.svelte create mode 100644 packages/svelte/tests/parser-modern/samples/switch-block-inside-switch-block/output.json create mode 100644 packages/svelte/tests/parser-modern/samples/switch-block-multiple-case/input.svelte create mode 100644 packages/svelte/tests/parser-modern/samples/switch-block-multiple-case/output.json create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-block-dependencies/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-block-dependencies/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/Seo.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-dependency-order/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-dependency-order/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-transition-inert/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-transition-inert/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-transition-undefined/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/switch-transition-undefined/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/switch-block-default/_config.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/switch-block-default/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/switch-block-default/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 ba091ef7ec41..882058283413 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -9,6 +9,7 @@ import read_pattern from '../read/context.js'; import read_expression, { get_loose_identifier } from '../read/expression.js'; import { create_fragment } from '../utils/create.js'; import { match_bracket } from '../utils/bracket.js'; +import { regex_whitespaces_strict } from '../../patterns.js'; const regex_whitespace_with_closing_curly_brace = /^\s*}/; @@ -78,6 +79,39 @@ function open(parser) { return; } + if (parser.eat('switch')) { + parser.require_whitespace(); + + /** @type {AST.SwitchBlock} */ + const block = parser.append({ + type: 'SwitchBlock', + start, + end: -1, + value: read_expression(parser), + consequences: [create_fragment()], + values: [null] + }); + + parser.allow_whitespace(); + + if (parser.eat('case')) { + if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) { + parser.allow_whitespace(); + } else { + parser.require_whitespace(); + block.values[0] = read_expression(parser); + parser.allow_whitespace(); + } + } + + parser.eat('}', true); + + parser.stack.push(block); + parser.fragments.push(block.consequences[0]); + + return; + } + if (parser.eat('each')) { parser.require_whitespace(); @@ -493,6 +527,32 @@ function next(parser) { return; } + if (block.type === 'SwitchBlock') { + if (parser.eat('case')) { + parser.require_whitespace(); + + const value = read_expression(parser); + + parser.allow_whitespace(); + parser.eat('}', true); + + let case_start = start - 1; + while (parser.template[case_start] !== '{') case_start -= 1; + + const consequent = create_fragment(); + + block.consequences.push(consequent); + block.values.push(value); + + parser.fragments.pop(); + parser.fragments.push(consequent); + } else { + e.expected_token(parser.index - 1, '{:case}'); + } + + return; + } + if (block.type === 'EachBlock') { if (!parser.eat('else')) e.expected_token(start, '{:else}'); @@ -584,6 +644,31 @@ function close(parser) { parser.pop(); return; + case 'SwitchBlock': + matched = parser.eat('switch', true, false); + + if (block.values[0] === null) { + const child_nodes = block.consequences[0].nodes; + + if ( + child_nodes.length === 0 || + (child_nodes.length === 1 && + child_nodes[0].type === 'Text' && + child_nodes[0].data.replace(regex_whitespaces_strict, ' ').trim() === '') + ) { + // in this situation we have an empty default case, we detect that and remove it + // {#switch show} + // {:case true} + block.consequences.shift(); + block.values.shift(); + } else { + // move default to end + block.values.push(/** @type {Expression | null} */ (block.values.shift())); + block.consequences.push(/** @type {AST.Fragment} */ (block.consequences.shift())); + } + } + break; + case 'EachBlock': matched = parser.eat('each', true, false); break; diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index 8a4d8cb35056..96a370120039 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -941,7 +941,7 @@ function get_possible_element_siblings(node, direction, adjacent_only, seen = ne } /** - * @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement | Compiler.AST.SnippetBlock | Compiler.AST.Component} node + * @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.SwitchBlock| Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement | Compiler.AST.SnippetBlock | Compiler.AST.Component} node * @param {Direction} direction * @param {boolean} adjacent_only * @param {Set} seen @@ -960,6 +960,10 @@ function get_possible_nested_siblings(node, direction, adjacent_only, seen = new fragments.push(node.consequent, node.alternate); break; + case 'SwitchBlock': + fragments.push(...node.consequences); + break; + case 'AwaitBlock': fragments.push(node.pending, node.then, node.catch); break; @@ -1087,11 +1091,12 @@ function loop_child(children, direction, adjacent_only, seen) { /** * @param {Compiler.AST.SvelteNode} node - * @returns {node is Compiler.AST.IfBlock | Compiler.AST.EachBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement} + * @returns {node is Compiler.AST.IfBlock | Compiler.AST.SwitchBlock | Compiler.AST.EachBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement} */ function is_block(node) { return ( node.type === 'IfBlock' || + node.type === 'SwitchBlock' || node.type === 'EachBlock' || node.type === 'AwaitBlock' || node.type === 'KeyBlock' || diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 52be9973748e..cfae232e9f2f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -43,6 +43,7 @@ import { FunctionExpression } from './visitors/FunctionExpression.js'; import { HtmlTag } from './visitors/HtmlTag.js'; import { Identifier } from './visitors/Identifier.js'; import { IfBlock } from './visitors/IfBlock.js'; +import { SwitchBlock } from './visitors/SwitchBlock.js'; import { ImportDeclaration } from './visitors/ImportDeclaration.js'; import { KeyBlock } from './visitors/KeyBlock.js'; import { LabeledStatement } from './visitors/LabeledStatement.js'; @@ -163,6 +164,7 @@ const visitors = { HtmlTag, Identifier, IfBlock, + SwitchBlock, ImportDeclaration, KeyBlock, LabeledStatement, @@ -733,6 +735,7 @@ export function analyze_component(root, source, options) { const type = path[j].type; if ( type === 'IfBlock' || + type === 'SwitchBlock' || type === 'EachBlock' || type === 'AwaitBlock' || type === 'KeyBlock' diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js index 77ea6549054b..89e3c6a6d075 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js @@ -18,6 +18,7 @@ export function ConstTag(node, context) { if ( parent?.type !== 'Fragment' || (grand_parent?.type !== 'IfBlock' && + grand_parent?.type !== 'SwitchBlock' && grand_parent?.type !== 'SvelteFragment' && grand_parent?.type !== 'Component' && grand_parent?.type !== 'SvelteComponent' && diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js index 4f41621db35b..8cb30baf2a60 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js @@ -123,6 +123,7 @@ export function RegularElement(node, context) { if ( ancestor.type === 'IfBlock' || + ancestor.type === 'SwitchBlock' || ancestor.type === 'EachBlock' || ancestor.type === 'AwaitBlock' || ancestor.type === 'KeyBlock' diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js index 652a447165c8..827ae583e627 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteSelf.js @@ -13,6 +13,7 @@ export function SvelteSelf(node, context) { const valid = context.path.some( (node) => node.type === 'IfBlock' || + node.type === 'SwitchBlock' || node.type === 'EachBlock' || node.type === 'Component' || node.type === 'SnippetBlock' diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/SwitchBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/SwitchBlock.js new file mode 100644 index 000000000000..be412ef1eba6 --- /dev/null +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SwitchBlock.js @@ -0,0 +1,34 @@ +/** @import { AST } from '#compiler' */ +/** @import { Context } from '../types' */ +import { mark_subtree_dynamic } from './shared/fragment.js'; +import { validate_block_not_empty, validate_opening_tag } from './shared/utils.js'; +import * as e from '../../../errors.js'; + +/** + * @param {AST.SwitchBlock} node + * @param {Context} context + */ +export function SwitchBlock(node, context) { + mark_subtree_dynamic(context.path); + node.consequences.forEach((consequence) => validate_block_not_empty(consequence, context)); + + if (context.state.analysis.runes) { + validate_opening_tag(node, context.state, '#'); + + for (const value of node.values) { + if (value === null) continue; + + const start = /** @type {number} */ (value.start); + const match = context.state.analysis.source + .substring(start - 10, start) + .match(/{(\s*):case\s+$/); + + if (match && match[1] !== '') { + // { :case ...} -- space after "{" not allowed + e.block_unexpected_character({ start: start - 10, end: start }, ':'); + } + } + } + + context.next(); +} 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 0dd4ae03b965..90ec73aea093 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 @@ -33,6 +33,7 @@ import { FunctionExpression } from './visitors/FunctionExpression.js'; import { HtmlTag } from './visitors/HtmlTag.js'; import { Identifier } from './visitors/Identifier.js'; import { IfBlock } from './visitors/IfBlock.js'; +import { SwitchBlock } from './visitors/SwitchBlock.js'; import { ImportDeclaration } from './visitors/ImportDeclaration.js'; import { KeyBlock } from './visitors/KeyBlock.js'; import { LabeledStatement } from './visitors/LabeledStatement.js'; @@ -111,6 +112,7 @@ const visitors = { HtmlTag, Identifier, IfBlock, + SwitchBlock, ImportDeclaration, KeyBlock, LabeledStatement, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js index 506fd4aafd82..7b6ee4ea18ef 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js @@ -31,6 +31,7 @@ export function BindDirective(node, context) { context.path.some( ({ type }) => type === 'IfBlock' || + type === 'SwitchBlock' || type === 'EachBlock' || type === 'AwaitBlock' || type === 'KeyBlock' diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/SwitchBlock.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SwitchBlock.js new file mode 100644 index 000000000000..da45088a438b --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/SwitchBlock.js @@ -0,0 +1,42 @@ +/** @import { BlockStatement, Expression } from 'estree' */ +/** @import { AST } from '#compiler' */ +/** @import { ComponentContext } from '../types' */ +import * as b from '../../../../utils/builders.js'; + +/** + * @param {AST.SwitchBlock} node + * @param {ComponentContext} context + */ +export function SwitchBlock(node, context) { + context.state.template.push_comment(); + const statements = []; + + const value = /** @type {Expression} */ (context.visit(node.value)); + + /** @type {Expression[]} */ + const args = [ + context.state.node, + b.arrow( + [b.id('$$render')], + b.block([ + b.switch_statement( + value, + node.consequences.map((node_consequent, index) => { + const consequent = /** @type {BlockStatement} */ (context.visit(node_consequent)); + const consequent_id = context.state.scope.generate('consequent'); + statements.push(b.var(b.id(consequent_id), b.arrow([b.id('$$anchor')], consequent))); + + return b.switch_case(node.values[index], [ + b.stmt(b.call(b.id('$$render'), b.id(consequent_id), b.literal(index))), + b.break() + ]); + }) + ) + ]) + ) + ]; + + statements.push(b.stmt(b.call('$.switch', ...args))); + + context.state.init.push(b.block(statements)); +} 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 b22b95f5aaaf..b7e3e3ecc998 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 @@ -22,6 +22,7 @@ import { Fragment } from './visitors/Fragment.js'; import { HtmlTag } from './visitors/HtmlTag.js'; import { Identifier } from './visitors/Identifier.js'; import { IfBlock } from './visitors/IfBlock.js'; +import { SwitchBlock } from './visitors/SwitchBlock.js'; import { KeyBlock } from './visitors/KeyBlock.js'; import { LabeledStatement } from './visitors/LabeledStatement.js'; import { MemberExpression } from './visitors/MemberExpression.js'; @@ -68,6 +69,7 @@ const template_visitors = { Fragment, HtmlTag, IfBlock, + SwitchBlock, KeyBlock, RegularElement, RenderTag, diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SwitchBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SwitchBlock.js new file mode 100644 index 000000000000..70caa9143f07 --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SwitchBlock.js @@ -0,0 +1,37 @@ +/** @import { BlockStatement, Expression } from 'estree' */ +/** @import { AST } from '#compiler' */ +/** @import { ComponentContext } from '../types.js' */ +import { HYDRATION_START } from '../../../../../constants.js'; +import * as b from '../../../../utils/builders.js'; +import { block_close } from './shared/utils.js'; + +/** + * @param {AST.SwitchBlock} node + * @param {ComponentContext} context + */ +export function SwitchBlock(node, context) { + const discriminant = /** @type {Expression} */ (context.visit(node.value)); + + const cases = node.consequences.map((node_consequent, index) => { + const consequent = /** @type {BlockStatement} */ (context.visit(node_consequent)); + consequent.body.unshift( + b.stmt(b.call(b.id('$$renderer.push'), b.literal(``))) + ); + consequent.body.push(b.break()); + + return b.switch_case(node.values[index], consequent.body); + }); + + // if there is no default block, we still have to create a hydration open marker + if (node.values.at(-1) !== null) { + const default_consequent = b.block([]); + default_consequent.body.unshift( + b.stmt( + b.call(b.id('$$renderer.push'), b.literal(``)) + ) + ); + cases.push(b.switch_case(null, default_consequent.body)); + } + + context.state.template.push(b.switch_statement(discriminant, cases), block_close); +} diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index dfc2ab1de140..0343b106139e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -407,6 +407,7 @@ function check_nodes_for_namespace(nodes, namespace) { if ( node.type === 'EachBlock' || node.type === 'IfBlock' || + node.type === 'SwitchBlock' || node.type === 'AwaitBlock' || node.type === 'Fragment' || node.type === 'KeyBlock' || diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 42048c3525f2..17860dd78693 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -478,6 +478,14 @@ export namespace AST { }; } + /** A `{#switch ...}` block */ + export interface SwitchBlock extends BaseNode { + type: 'SwitchBlock'; + value: Expression; + consequences: Array; + values: Array; + } + /** An `{#await ...}` block */ export interface AwaitBlock extends BaseNode { type: 'AwaitBlock'; @@ -579,6 +587,7 @@ export namespace AST { export type Block = | AST.EachBlock | AST.IfBlock + | AST.SwitchBlock | AST.AwaitBlock | AST.KeyBlock | AST.SnippetBlock; diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js index 99306ce4d966..2b0bdde06dcd 100644 --- a/packages/svelte/src/compiler/utils/builders.js +++ b/packages/svelte/src/compiler/utils/builders.js @@ -610,6 +610,32 @@ function if_builder(test, consequent, alternate) { return { type: 'IfStatement', test, consequent, alternate }; } +/** + * @param {ESTree.Expression} discriminant + * @param {ESTree.SwitchCase[]} cases + * @returns {ESTree.SwitchStatement} + */ +function switch_statement_builder(discriminant, cases) { + return { type: 'SwitchStatement', discriminant, cases }; +} + +/** + * @param {ESTree.Identifier | null} label + * @returns {ESTree.BreakStatement} + */ +function break_builder(label = null) { + return { type: 'BreakStatement', label }; +} + +/** + * @param {ESTree.Expression | null} test + * @param {ESTree.Statement[]} consequent + * @returns {ESTree.SwitchCase} + */ +function switch_case_builder(test, consequent) { + return { type: 'SwitchCase', test, consequent }; +} + /** * @param {string} as * @param {string} source @@ -672,6 +698,9 @@ export { function_builder as function, return_builder as return, if_builder as if, + switch_statement_builder as switch_statement, + switch_case_builder as switch_case, + break_builder as break, this_instance as this, null_instance as null, debugger_builder as debugger diff --git a/packages/svelte/src/internal/client/dom/blocks/switch.js b/packages/svelte/src/internal/client/dom/blocks/switch.js new file mode 100644 index 000000000000..18936280d2af --- /dev/null +++ b/packages/svelte/src/internal/client/dom/blocks/switch.js @@ -0,0 +1,66 @@ +/** @import { Effect, TemplateNode } from '#client' */ +import { + hydrate_next, + hydrating, + read_hydration_instruction, + set_hydrate_node, + set_hydrating, + skip_nodes +} from '../hydration.js'; +import { block } from '../../reactivity/effects.js'; +import { HYDRATION_START } from '../../../../constants.js'; +import { BranchManager } from './branches.js'; + +/** + * @param {TemplateNode} node + * @param {(branch: (fn: (anchor: Node) => void, index: number) => void) => void} fn + * @returns {void} + */ +export function switch_block(node, fn) { + if (hydrating) { + hydrate_next(); + } + + var branches = new BranchManager(node); + + /** + * @param {number} index, + * @param {null | ((anchor: Node) => void)} fn + */ + function update_branch(index, fn) { + if (hydrating) { + const hydration_tag = read_hydration_instruction(node); + const hydration_index = Number(hydration_tag.slice(HYDRATION_START.length)); + + if (index !== hydration_index) { + // Hydration mismatch: remove everything inside the anchor and start fresh. + // This could happen with `{#switch browser}...{/switch}`, for example + var anchor = skip_nodes(); + + set_hydrate_node(anchor); + branches.anchor = anchor; + + set_hydrating(false); + branches.ensure(index, fn); + set_hydrating(true); + + return; + } + } + + branches.ensure(index, fn); + } + + block(() => { + var has_branch = false; + + fn((fn, index) => { + has_branch = true; + update_branch(index, fn); + }); + + if (!has_branch) { + update_branch(-1, null); + } + }); +} diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 5df9536d4d6a..070cfc778878 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -95,7 +95,7 @@ export function skip_nodes(remove = true) { if (data === HYDRATION_END) { if (depth === 0) return node; depth -= 1; - } else if (data === HYDRATION_START || data === HYDRATION_START_ELSE) { + } else if (data[0] === HYDRATION_START) { depth += 1; } } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 471eed299d2a..ca8481133f62 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -13,6 +13,7 @@ export { async } from './dom/blocks/async.js'; export { validate_snippet_args } from './dev/validation.js'; export { await_block as await } from './dom/blocks/await.js'; export { if_block as if } from './dom/blocks/if.js'; +export { switch_block as switch } from './dom/blocks/switch.js'; export { key } from './dom/blocks/key.js'; export { css_props } from './dom/blocks/css-props.js'; export { index, each } from './dom/blocks/each.js'; diff --git a/packages/svelte/tests/parser-modern/samples/switch-block-default/input.svelte b/packages/svelte/tests/parser-modern/samples/switch-block-default/input.svelte new file mode 100644 index 000000000000..2dfb69cbaf1a --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/switch-block-default/input.svelte @@ -0,0 +1,3 @@ +{#switch foo case "bar"} +

foo

+{/switch} diff --git a/packages/svelte/tests/parser-modern/samples/switch-block-default/output.json b/packages/svelte/tests/parser-modern/samples/switch-block-default/output.json new file mode 100644 index 000000000000..038edff93e9a --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/switch-block-default/output.json @@ -0,0 +1,93 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 46, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "SwitchBlock", + "start": 0, + "end": 46, + "value": { + "type": "Identifier", + "start": 9, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "foo" + }, + "consequences": [ + { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 24, + "end": 26, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 26, + "end": 36, + "name": "p", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 29, + "end": 32, + "raw": "foo", + "data": "foo" + } + ] + } + }, + { + "type": "Text", + "start": 36, + "end": 37, + "raw": "\n", + "data": "\n" + } + ] + } + ], + "values": [ + { + "type": "Literal", + "start": 18, + "end": 23, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 23 + } + }, + "value": "bar", + "raw": "\"bar\"" + } + ] + } + ] + }, + "options": null +} diff --git a/packages/svelte/tests/parser-modern/samples/switch-block-empty/input.svelte b/packages/svelte/tests/parser-modern/samples/switch-block-empty/input.svelte new file mode 100644 index 000000000000..6cd2c147f489 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/switch-block-empty/input.svelte @@ -0,0 +1 @@ +{#switch foo}bar{/switch} diff --git a/packages/svelte/tests/parser-modern/samples/switch-block-empty/output.json b/packages/svelte/tests/parser-modern/samples/switch-block-empty/output.json new file mode 100644 index 000000000000..6af374dc9410 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/switch-block-empty/output.json @@ -0,0 +1,49 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 25, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "SwitchBlock", + "start": 0, + "end": 25, + "value": { + "type": "Identifier", + "start": 9, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "foo" + }, + "consequences": [ + { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 13, + "end": 16, + "raw": "bar", + "data": "bar" + } + ] + } + ], + "values": [null] + } + ] + }, + "options": null +} diff --git a/packages/svelte/tests/parser-modern/samples/switch-block-inside-switch-block/input.svelte b/packages/svelte/tests/parser-modern/samples/switch-block-inside-switch-block/input.svelte new file mode 100644 index 000000000000..3b505465dd1d --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/switch-block-inside-switch-block/input.svelte @@ -0,0 +1,7 @@ +{#switch foo} +{:case "foo"} + {#switch bar} + {:case "bar"} +

bar

+ {/switch} +{/switch} diff --git a/packages/svelte/tests/parser-modern/samples/switch-block-inside-switch-block/output.json b/packages/svelte/tests/parser-modern/samples/switch-block-inside-switch-block/output.json new file mode 100644 index 000000000000..2d3627698f4c --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/switch-block-inside-switch-block/output.json @@ -0,0 +1,154 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 91, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "SwitchBlock", + "start": 0, + "end": 91, + "value": { + "type": "Identifier", + "start": 9, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "foo" + }, + "consequences": [ + { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 27, + "end": 29, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "SwitchBlock", + "start": 29, + "end": 81, + "value": { + "type": "Identifier", + "start": 38, + "end": 41, + "loc": { + "start": { + "line": 3, + "column": 10 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "name": "bar" + }, + "consequences": [ + { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 57, + "end": 60, + "raw": "\n\t\t", + "data": "\n\t\t" + }, + { + "type": "RegularElement", + "start": 60, + "end": 70, + "name": "p", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 63, + "end": 66, + "raw": "bar", + "data": "bar" + } + ] + } + }, + { + "type": "Text", + "start": 70, + "end": 72, + "raw": "\n\t", + "data": "\n\t" + } + ] + } + ], + "values": [ + { + "type": "Literal", + "start": 51, + "end": 56, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 13 + } + }, + "value": "bar", + "raw": "\"bar\"" + } + ] + }, + { + "type": "Text", + "start": 81, + "end": 82, + "raw": "\n", + "data": "\n" + } + ] + } + ], + "values": [ + { + "type": "Literal", + "start": 21, + "end": 26, + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 12 + } + }, + "value": "foo", + "raw": "\"foo\"" + } + ] + } + ] + }, + "options": null +} diff --git a/packages/svelte/tests/parser-modern/samples/switch-block-multiple-case/input.svelte b/packages/svelte/tests/parser-modern/samples/switch-block-multiple-case/input.svelte new file mode 100644 index 000000000000..d343e87942aa --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/switch-block-multiple-case/input.svelte @@ -0,0 +1,6 @@ +{#switch foo} +{:case "bar"} +

foo

+{:case "baz"} +

baz

+{/switch} diff --git a/packages/svelte/tests/parser-modern/samples/switch-block-multiple-case/output.json b/packages/svelte/tests/parser-modern/samples/switch-block-multiple-case/output.json new file mode 100644 index 000000000000..cee3b2d58396 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/switch-block-multiple-case/output.json @@ -0,0 +1,148 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 75, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "SwitchBlock", + "start": 0, + "end": 75, + "value": { + "type": "Identifier", + "start": 9, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "foo" + }, + "consequences": [ + { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 27, + "end": 29, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 29, + "end": 39, + "name": "p", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 32, + "end": 35, + "raw": "foo", + "data": "foo" + } + ] + } + }, + { + "type": "Text", + "start": 39, + "end": 40, + "raw": "\n", + "data": "\n" + } + ] + }, + { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 53, + "end": 55, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "RegularElement", + "start": 55, + "end": 65, + "name": "p", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 58, + "end": 61, + "raw": "baz", + "data": "baz" + } + ] + } + }, + { + "type": "Text", + "start": 65, + "end": 66, + "raw": "\n", + "data": "\n" + } + ] + } + ], + "values": [ + { + "type": "Literal", + "start": 21, + "end": 26, + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 12 + } + }, + "value": "bar", + "raw": "\"bar\"" + }, + { + "type": "Literal", + "start": 47, + "end": 52, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 4, + "column": 12 + } + }, + "value": "baz", + "raw": "\"baz\"" + } + ] + } + ] + }, + "options": null +} diff --git a/packages/svelte/tests/runtime-runes/samples/switch-block-dependencies/_config.js b/packages/svelte/tests/runtime-runes/samples/switch-block-dependencies/_config.js new file mode 100644 index 000000000000..b5847f3f7b63 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-block-dependencies/_config.js @@ -0,0 +1,19 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + await Promise.resolve(); + + let [btn1] = target.querySelectorAll('button'); + + flushSync(() => { + btn1?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `false\ntrue\n\nfirst:\nfalse\n
\nsecond:\ntrue` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/switch-block-dependencies/main.svelte b/packages/svelte/tests/runtime-runes/samples/switch-block-dependencies/main.svelte new file mode 100644 index 000000000000..8aa8ee9cf980 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-block-dependencies/main.svelte @@ -0,0 +1,22 @@ + + +{first} {second} + + + +{#switch first || derivedSecond} +{:case true} + first: {first} +
+ second: {derivedSecond} +{/switch} diff --git a/packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/Seo.svelte b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/Seo.svelte new file mode 100644 index 000000000000..6176f3eefc61 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/Seo.svelte @@ -0,0 +1,9 @@ + + + + {post.title} + + +

{post.title}

diff --git a/packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/_config.js b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/_config.js new file mode 100644 index 000000000000..83b58d0f7b22 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/_config.js @@ -0,0 +1,22 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, window }) { + const [btn1] = target.querySelectorAll('button'); + + assert.htmlEqual(window.document.head.innerHTML, ``); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual(window.document.head.innerHTML, `hello world`); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual(window.document.head.innerHTML, `hello world`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/main.svelte new file mode 100644 index 000000000000..f561f92a9b3b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order-2/main.svelte @@ -0,0 +1,17 @@ + + + + +{#switch !!post case true} + +{/switch} diff --git a/packages/svelte/tests/runtime-runes/samples/switch-dependency-order/_config.js b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order/_config.js new file mode 100644 index 000000000000..5b96f3f4c512 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: `

expires in 1 click

`, + + async test({ assert, target }) { + const [btn1] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/switch-dependency-order/main.svelte b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order/main.svelte new file mode 100644 index 000000000000..380d676589d5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-dependency-order/main.svelte @@ -0,0 +1,13 @@ + + +{#switch data?.num case 1} + +

expires in {data.num} click

+{/switch} diff --git a/packages/svelte/tests/runtime-runes/samples/switch-transition-inert/_config.js b/packages/svelte/tests/runtime-runes/samples/switch-transition-inert/_config.js new file mode 100644 index 000000000000..7a1673786c00 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-transition-inert/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: `
hello
`, + + async test({ assert, target }) { + const [btn1, btn2] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual(target.innerHTML, `
hello
`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/switch-transition-inert/main.svelte b/packages/svelte/tests/runtime-runes/samples/switch-transition-inert/main.svelte new file mode 100644 index 000000000000..7fbdfba98234 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-transition-inert/main.svelte @@ -0,0 +1,16 @@ + + + + +{#switch !!state case true} +
+ {#if true} + {state} + {/if} +
+{/switch} + diff --git a/packages/svelte/tests/runtime-runes/samples/switch-transition-undefined/_config.js b/packages/svelte/tests/runtime-runes/samples/switch-transition-undefined/_config.js new file mode 100644 index 000000000000..3098714df477 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-transition-undefined/_config.js @@ -0,0 +1,40 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const [btn1, btn2] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `

Hello\n!

` + ); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual(target.innerHTML, ``); + + flushSync(() => { + btn2.click(); + }); + + assert.htmlEqual(target.innerHTML, ``); + + flushSync(() => { + btn1.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `

Hello\n!

` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/switch-transition-undefined/main.svelte b/packages/svelte/tests/runtime-runes/samples/switch-transition-undefined/main.svelte new file mode 100644 index 000000000000..cfe2cc1cb88d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/switch-transition-undefined/main.svelte @@ -0,0 +1,16 @@ + + + + +{#switch show case true} +

Hello {name}!

+{/switch} diff --git a/packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/_config.js b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/_config.js new file mode 100644 index 000000000000..e817c8198385 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + props: { + foo: true + } +}); diff --git a/packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/_expected.html b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/_expected.html new file mode 100644 index 000000000000..b6f3d8a3307d --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/_expected.html @@ -0,0 +1 @@ +

foo is true

diff --git a/packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/main.svelte b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/main.svelte new file mode 100644 index 000000000000..96222e944a52 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-match/main.svelte @@ -0,0 +1,7 @@ + + +{#switch foo case true} +

foo is true

+{/switch} diff --git a/packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/_config.js b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/_config.js new file mode 100644 index 000000000000..466cb4201c19 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + props: { + foo: false + } +}); diff --git a/packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/_expected.html b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/_expected.html new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/main.svelte b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/main.svelte new file mode 100644 index 000000000000..126b389b8cdc --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/switch-block-case-no-match/main.svelte @@ -0,0 +1,7 @@ + + +{#switch foo case true} +

foo is false

+{/switch} diff --git a/packages/svelte/tests/server-side-rendering/samples/switch-block-default/_config.js b/packages/svelte/tests/server-side-rendering/samples/switch-block-default/_config.js new file mode 100644 index 000000000000..466cb4201c19 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/switch-block-default/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + props: { + foo: false + } +}); diff --git a/packages/svelte/tests/server-side-rendering/samples/switch-block-default/_expected.html b/packages/svelte/tests/server-side-rendering/samples/switch-block-default/_expected.html new file mode 100644 index 000000000000..f1742b5d1f4e --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/switch-block-default/_expected.html @@ -0,0 +1 @@ +

default

diff --git a/packages/svelte/tests/server-side-rendering/samples/switch-block-default/main.svelte b/packages/svelte/tests/server-side-rendering/samples/switch-block-default/main.svelte new file mode 100644 index 000000000000..2366f758f325 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/switch-block-default/main.svelte @@ -0,0 +1,7 @@ + + +{#switch foo} +

default

+{/switch} diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index d260b738c3cf..ef09bad011ab 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1453,6 +1453,14 @@ declare module 'svelte/compiler' { alternate: Fragment | null; } + /** A `{#switch ...}` block */ + export interface SwitchBlock extends BaseNode { + type: 'SwitchBlock'; + value: Expression; + consequences: Array; + values: Array; + } + /** An `{#await ...}` block */ export interface AwaitBlock extends BaseNode { type: 'AwaitBlock'; @@ -1528,6 +1536,7 @@ declare module 'svelte/compiler' { export type Block = | AST.EachBlock | AST.IfBlock + | AST.SwitchBlock | AST.AwaitBlock | AST.KeyBlock | AST.SnippetBlock;