diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 00070e563c8..a84bfd64cef 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -465,16 +465,16 @@ The `{@const ...}` tag defines a local constant. ```sv {#each boxes as box} - {@const area = box.width * box.height} + {@const area = box.width * box.height} {box.width} * {box.height} = {area} {/each} ``` -`{@const}` is only allowed as direct child of `{#each}`, `{:then}`, `{:catch}`, `` or ``. +`{@const}` is only allowed as direct child of `{#if}`, `{:else if}`, `{:else}`, `{#each}`, `{:then}`, `{:catch}`, `` or ``. ### Element directives diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index 3099e6cca1c..ea95c8cbecf 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -260,7 +260,7 @@ export default { }, invalid_const_placement: { code: 'invalid-const-placement', - message: '{@const} must be the immediate child of {#each}, {:then}, {:catch}, or ' + message: '{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or ' }, invalid_const_declaration: (name: string) => ({ code: 'invalid-const-declaration', diff --git a/src/compiler/compile/nodes/ConstTag.ts b/src/compiler/compile/nodes/ConstTag.ts index 157361fb676..87a90390085 100644 --- a/src/compiler/compile/nodes/ConstTag.ts +++ b/src/compiler/compile/nodes/ConstTag.ts @@ -11,7 +11,7 @@ import is_reference, { NodeWithPropertyDefinition } from 'is-reference'; import get_object from '../utils/get_object'; import compiler_errors from '../compiler_errors'; -const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate']); +const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate', 'IfBlock', 'ElseBlock']); export default class ConstTag extends Node { type: 'ConstTag'; diff --git a/src/compiler/compile/nodes/ElseBlock.ts b/src/compiler/compile/nodes/ElseBlock.ts index db5c649845d..fb3661c7ab2 100644 --- a/src/compiler/compile/nodes/ElseBlock.ts +++ b/src/compiler/compile/nodes/ElseBlock.ts @@ -1,16 +1,21 @@ -import map_children from './shared/map_children'; import AbstractBlock from './shared/AbstractBlock'; import Component from '../Component'; import TemplateScope from './shared/TemplateScope'; import { TemplateNode } from '../../interfaces'; import Node from './shared/Node'; +import ConstTag from './ConstTag'; +import get_const_tags from './shared/get_const_tags'; export default class ElseBlock extends AbstractBlock { type: 'ElseBlock'; + scope: TemplateScope; + const_tags: ConstTag[]; constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); - this.children = map_children(component, this, scope, info.children); + + this.scope = scope.child(); + ([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); this.warn_if_empty_block(); } diff --git a/src/compiler/compile/nodes/IfBlock.ts b/src/compiler/compile/nodes/IfBlock.ts index 85d5d6b36e3..8351f1e0e27 100644 --- a/src/compiler/compile/nodes/IfBlock.ts +++ b/src/compiler/compile/nodes/IfBlock.ts @@ -1,22 +1,26 @@ import ElseBlock from './ElseBlock'; import Expression from './shared/Expression'; -import map_children from './shared/map_children'; import AbstractBlock from './shared/AbstractBlock'; import Component from '../Component'; import TemplateScope from './shared/TemplateScope'; import { TemplateNode } from '../../interfaces'; import Node from './shared/Node'; +import ConstTag from './ConstTag'; +import get_const_tags from './shared/get_const_tags'; export default class IfBlock extends AbstractBlock { type: 'IfBlock'; expression: Expression; else: ElseBlock; + scope: TemplateScope; + const_tags: ConstTag[]; constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); + this.scope = scope.child(); - this.expression = new Expression(component, this, scope, info.expression); - this.children = map_children(component, this, scope, info.children); + this.expression = new Expression(component, this, this.scope, info.expression); + ([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); this.else = info.else ? new ElseBlock(component, this, scope, info.else) diff --git a/src/compiler/compile/nodes/interfaces.ts b/src/compiler/compile/nodes/interfaces.ts index 2c636b36f38..f023cad25c9 100644 --- a/src/compiler/compile/nodes/interfaces.ts +++ b/src/compiler/compile/nodes/interfaces.ts @@ -72,6 +72,8 @@ export type INode = Action | Window; export type INodeAllowConstTag = +| IfBlock +| ElseBlock | EachBlock | CatchBlock | ThenBlock diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index b77b2472df5..db7929e51bc 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -27,6 +27,7 @@ export class ElseBlockWrapper extends Wrapper { next_sibling: Wrapper ) { super(renderer, block, parent, node); + add_const_tags_context(renderer, this.node.const_tags); this.block = block.child({ comment: create_debugging_comment(node, this.renderer.component), @@ -257,6 +258,18 @@ export default class EachBlockWrapper extends Wrapper { } if (this.else) { + let else_ctx = x`#ctx`; + if (this.else.node.const_tags.length > 0) { + const get_ctx_name = this.renderer.component.get_unique_name('get_else_ctx'); + this.renderer.blocks.push(b` + function ${get_ctx_name}(#ctx) { + const child_ctx = #ctx.slice(); + ${add_const_tags(block, this.else.node.const_tags, 'child_ctx')} + return child_ctx; + } + `); + else_ctx = x`${get_ctx_name}(#ctx)`; + } const each_block_else = component.get_unique_name(`${this.var.name}_else`); block.chunks.init.push(b`let ${each_block_else} = null;`); @@ -264,7 +277,7 @@ export default class EachBlockWrapper extends Wrapper { // TODO neaten this up... will end up with an empty line in the block block.chunks.init.push(b` if (!${this.vars.data_length}) { - ${each_block_else} = ${this.else.block.name}(#ctx); + ${each_block_else} = ${this.else.block.name}(${else_ctx}); } `); @@ -304,9 +317,9 @@ export default class EachBlockWrapper extends Wrapper { if (this.else.block.has_update_method) { this.updates.push(b` if (!${this.vars.data_length} && ${each_block_else}) { - ${each_block_else}.p(#ctx, #dirty); + ${each_block_else}.p(${else_ctx}, #dirty); } else if (!${this.vars.data_length}) { - ${each_block_else} = ${this.else.block.name}(#ctx); + ${each_block_else} = ${this.else.block.name}(${else_ctx}); ${each_block_else}.c(); ${has_transitions && b`@transition_in(${each_block_else}, 1);`} ${each_block_else}.m(${update_mount_node}, ${update_anchor_node}); @@ -321,7 +334,7 @@ export default class EachBlockWrapper extends Wrapper { ${destroy_block_else}; } } else if (!${each_block_else}) { - ${each_block_else} = ${this.else.block.name}(#ctx); + ${each_block_else} = ${this.else.block.name}(${else_ctx}); ${each_block_else}.c(); ${has_transitions && b`@transition_in(${each_block_else}, 1);`} ${each_block_else}.m(${update_mount_node}, ${update_anchor_node}); diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index e94404290f2..d74ce270bfe 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -11,6 +11,7 @@ import { walk } from 'estree-walker'; import { is_head } from './shared/is_head'; import { Identifier, Node } from 'estree'; import { push_array } from '../../../utils/push_array'; +import { add_const_tags, add_const_tags_context } from './shared/add_const_tags'; function is_else_if(node: ElseBlock) { return ( @@ -25,8 +26,10 @@ class IfBlockBranch extends Wrapper { condition?: any; snippet?: Node; is_dynamic: boolean; + node: IfBlock | ElseBlock; var = null; + get_ctx_name: Node | undefined; constructor( renderer: Renderer, @@ -63,6 +66,8 @@ class IfBlockBranch extends Wrapper { } } + add_const_tags_context(renderer, this.node.const_tags); + this.block = block.child({ comment: create_debugging_comment(node, parent.renderer.component), name: parent.renderer.component.get_unique_name( @@ -74,6 +79,10 @@ class IfBlockBranch extends Wrapper { this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling); this.is_dynamic = this.block.dependencies.size > 0; + + if (node.const_tags.length > 0) { + this.get_ctx_name = parent.renderer.component.get_unique_name(is_else ? 'get_else_ctx' : 'get_if_ctx'); + } } } @@ -190,6 +199,18 @@ export default class IfBlockWrapper extends Wrapper { const has_outros = this.branches[0].block.has_outro_method; const has_transitions = has_intros || has_outros; + this.branches.forEach(branch => { + if (branch.get_ctx_name) { + this.renderer.blocks.push(b` + function ${branch.get_ctx_name}(#ctx) { + const child_ctx = #ctx.slice(); + ${add_const_tags(block, branch.node.const_tags, 'child_ctx')} + return child_ctx; + } + `); + } + }); + const vars = { name, anchor, if_exists_condition, has_else, has_transitions }; const detaching = parent_node && !is_head(parent_node) ? null : 'detaching'; @@ -260,9 +281,13 @@ export default class IfBlockWrapper extends Wrapper { ) { const select_block_type = this.renderer.component.get_unique_name('select_block_type'); const current_block_type = block.get_unique_name('current_block_type'); + const need_select_block_ctx = this.branches.some(branch => branch.get_ctx_name); + const select_block_ctx = need_select_block_ctx ? block.get_unique_name('select_block_ctx') : null; + const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type})` : x`#ctx`; + const get_block = has_else - ? x`${current_block_type}(#ctx)` - : x`${current_block_type} && ${current_block_type}(#ctx)`; + ? x`${current_block_type}(${if_ctx})` + : x`${current_block_type} && ${current_block_type}(${if_ctx})`; if (this.needs_update) { block.chunks.init.push(b` @@ -271,10 +296,10 @@ export default class IfBlockWrapper extends Wrapper { return b`${snippet && dependencies.length > 0 ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;` : null}`; })} ${this.branches.map(({ condition, snippet, block }) => condition - ? b` - ${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`} - if (${condition}) return ${block.name};` - : b`return ${block.name};` + ? b` + ${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`} + if (${condition}) return ${block.name};` + : b`return ${block.name};` )} } `); @@ -282,12 +307,40 @@ export default class IfBlockWrapper extends Wrapper { block.chunks.init.push(b` function ${select_block_type}(#ctx, #dirty) { ${this.branches.map(({ condition, snippet, block }) => condition - ? b`if (${snippet || condition}) return ${block.name};` - : b`return ${block.name};`)} + ? b`if (${snippet || condition}) return ${block.name};` + : b`return ${block.name};`)} } `); } + if (need_select_block_ctx) { + // if all branches needs create a context + if (this.branches.every(branch => branch.get_ctx_name)) { + block.chunks.init.push(b` + function ${select_block_ctx}(#ctx, #type) { + ${this.branches.map(({ condition, get_ctx_name, block }) => { + return condition + ? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);` + : b`return ${get_ctx_name}(#ctx);`; + }).filter(Boolean)} + } + `); + } else { + // when not all branches need to create a new context, + // this code is simpler + block.chunks.init.push(b` + function ${select_block_ctx}(#ctx, #type) { + ${this.branches.map(({ get_ctx_name, block }) => { + return get_ctx_name + ? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);` + : null; + }).filter(Boolean)} + return #ctx; + } + `); + } + } + block.chunks.init.push(b` let ${current_block_type} = ${select_block_type}(#ctx, ${this.renderer.get_initial_dirty()}); let ${name} = ${get_block}; @@ -322,7 +375,7 @@ export default class IfBlockWrapper extends Wrapper { if (dynamic) { block.chunks.update.push(b` if (${current_block_type} === (${current_block_type} = ${select_block_type}(#ctx, #dirty)) && ${name}) { - ${name}.p(#ctx, #dirty); + ${name}.p(${if_ctx}, #dirty); } else { ${change_block} } @@ -336,9 +389,9 @@ export default class IfBlockWrapper extends Wrapper { } } else if (dynamic) { if (if_exists_condition) { - block.chunks.update.push(b`if (${if_exists_condition}) ${name}.p(#ctx, #dirty);`); + block.chunks.update.push(b`if (${if_exists_condition}) ${name}.p(${if_ctx}, #dirty);`); } else { - block.chunks.update.push(b`${name}.p(#ctx, #dirty);`); + block.chunks.update.push(b`${name}.p(${if_ctx}, #dirty);`); } } @@ -370,6 +423,9 @@ export default class IfBlockWrapper extends Wrapper { const previous_block_index = block.get_unique_name('previous_block_index'); const if_block_creators = block.get_unique_name('if_block_creators'); const if_blocks = block.get_unique_name('if_blocks'); + const need_select_block_ctx = this.branches.some(branch => branch.get_ctx_name); + const select_block_ctx = need_select_block_ctx ? block.get_unique_name('select_block_ctx') : null; + const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type_index})` : x`#ctx`; const if_current_block_type_index = has_else ? nodes => nodes @@ -392,32 +448,60 @@ export default class IfBlockWrapper extends Wrapper { return b`${snippet && dependencies.length > 0 ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;` : null}`; })} ${this.branches.map(({ condition, snippet }, i) => condition - ? b` - ${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`} - if (${condition}) return ${i};` - : b`return ${i};`)} - ${!has_else && b`return -1;`} - } - ` + ? b` + ${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`} + if (${condition}) return ${i};` + : b`return ${i};`)} + ${!has_else && b`return -1;`} + } + ` : b` function ${select_block_type}(#ctx, #dirty) { ${this.branches.map(({ condition, snippet }, i) => condition - ? b`if (${snippet || condition}) return ${i};` - : b`return ${i};`)} + ? b`if (${snippet || condition}) return ${i};` + : b`return ${i};`)} ${!has_else && b`return -1;`} } `} `); + if (need_select_block_ctx) { + // if all branches needs create a context + if (this.branches.every(branch => branch.get_ctx_name)) { + block.chunks.init.push(b` + function ${select_block_ctx}(#ctx, #index) { + ${this.branches.map(({ condition, get_ctx_name }, i) => { + return condition + ? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);` + : b`return ${get_ctx_name}(#ctx);`; + }).filter(Boolean)} + } + `); + } else { + // when not all branches need to create a new context, + // this code is simpler + block.chunks.init.push(b` + function ${select_block_ctx}(#ctx, #index) { + ${this.branches.map(({ get_ctx_name }, i) => { + return get_ctx_name + ? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);` + : null; + }).filter(Boolean)} + return #ctx; + } + `); + } + } + if (has_else) { block.chunks.init.push(b` ${current_block_type_index} = ${select_block_type}(#ctx, ${this.renderer.get_initial_dirty()}); - ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx); + ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](${if_ctx}); `); } else { block.chunks.init.push(b` if (~(${current_block_type_index} = ${select_block_type}(#ctx, ${this.renderer.get_initial_dirty()}))) { - ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx); + ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](${if_ctx}); } `); } @@ -445,10 +529,10 @@ export default class IfBlockWrapper extends Wrapper { const create_new_block = b` ${name} = ${if_blocks}[${current_block_type_index}]; if (!${name}) { - ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](#ctx); + ${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](${if_ctx}); ${name}.c(); } else { - ${dynamic && b`${name}.p(#ctx, #dirty);`} + ${dynamic && b`${name}.p(${if_ctx}, #dirty);`} } ${has_transitions && b`@transition_in(${name}, 1);`} ${name}.m(${update_mount_node}, ${anchor}); @@ -480,7 +564,7 @@ export default class IfBlockWrapper extends Wrapper { if (dynamic) { block.chunks.update.push(b` if (${current_block_type_index} === ${previous_block_index}) { - ${if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].p(#ctx, #dirty);`)} + ${if_current_block_type_index(b`${if_blocks}[${current_block_type_index}].p(${if_ctx}, #dirty);`)} } else { ${change_block} } @@ -494,9 +578,9 @@ export default class IfBlockWrapper extends Wrapper { } } else if (dynamic) { if (if_exists_condition) { - block.chunks.update.push(b`if (${if_exists_condition}) ${name}.p(#ctx, #dirty);`); + block.chunks.update.push(b`if (${if_exists_condition}) ${name}.p(${if_ctx}, #dirty);`); } else { - block.chunks.update.push(b`${name}.p(#ctx, #dirty);`); + block.chunks.update.push(b`${name}.p(${if_ctx}, #dirty);`); } } @@ -514,11 +598,12 @@ export default class IfBlockWrapper extends Wrapper { detaching ) { const branch = this.branches[0]; + const if_ctx = branch.get_ctx_name ? x`${branch.get_ctx_name}(#ctx)` : x`#ctx`; if (branch.snippet) block.add_variable(branch.condition, branch.snippet); block.chunks.init.push(b` - let ${name} = ${branch.condition} && ${branch.block.name}(#ctx); + let ${name} = ${branch.condition} && ${branch.block.name}(${if_ctx}); `); const initial_mount_node = parent_node || '#target'; @@ -533,15 +618,14 @@ export default class IfBlockWrapper extends Wrapper { const enter = b` if (${name}) { - ${dynamic && b`${name}.p(#ctx, #dirty);`} - ${ - has_transitions && + ${dynamic && b`${name}.p(${if_ctx}, #dirty);`} + ${has_transitions && b`if (${block.renderer.dirty(branch.dependencies)}) { - @transition_in(${name}, 1); - }` - } + @transition_in(${name}, 1); + }` + } } else { - ${name} = ${branch.block.name}(#ctx); + ${name} = ${branch.block.name}(${if_ctx}); ${name}.c(); ${has_transitions && b`@transition_in(${name}, 1);`} ${name}.m(${update_mount_node}, ${anchor}); @@ -578,7 +662,7 @@ export default class IfBlockWrapper extends Wrapper { } } else if (dynamic) { block.chunks.update.push(b` - if (${branch.condition}) ${name}.p(#ctx, #dirty); + if (${branch.condition}) ${name}.p(${if_ctx}, #dirty); `); } diff --git a/src/compiler/compile/render_ssr/handlers/EachBlock.ts b/src/compiler/compile/render_ssr/handlers/EachBlock.ts index 008c023e111..2b9149ffefa 100644 --- a/src/compiler/compile/render_ssr/handlers/EachBlock.ts +++ b/src/compiler/compile/render_ssr/handlers/EachBlock.ts @@ -2,6 +2,7 @@ import Renderer, { RenderOptions } from '../Renderer'; import EachBlock from '../../nodes/EachBlock'; import { x } from 'code-red'; import { get_const_tags } from './shared/get_const_tags'; +import { Node } from 'estree'; export default function(node: EachBlock, renderer: Renderer, options: RenderOptions) { const args = [node.context_node]; @@ -16,7 +17,8 @@ export default function(node: EachBlock, renderer: Renderer, options: RenderOpti if (node.else) { renderer.push(); renderer.render(node.else.children, options); - const alternate = renderer.pop(); + let alternate: Node = renderer.pop(); + if (node.else.const_tags.length > 0) alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`; renderer.add_expression(x`${node.expression.node}.length ? ${consequent} : ${alternate}`); } else { diff --git a/src/compiler/compile/render_ssr/handlers/IfBlock.ts b/src/compiler/compile/render_ssr/handlers/IfBlock.ts index 504a237bc5e..b242775fe69 100644 --- a/src/compiler/compile/render_ssr/handlers/IfBlock.ts +++ b/src/compiler/compile/render_ssr/handlers/IfBlock.ts @@ -1,17 +1,21 @@ import IfBlock from '../../nodes/IfBlock'; import Renderer, { RenderOptions } from '../Renderer'; import { x } from 'code-red'; +import { get_const_tags } from './shared/get_const_tags'; +import { Node } from 'estree'; -export default function(node: IfBlock, renderer: Renderer, options: RenderOptions) { +export default function (node: IfBlock, renderer: Renderer, options: RenderOptions) { const condition = node.expression.node; renderer.push(); renderer.render(node.children, options); - const consequent = renderer.pop(); + let consequent: Node = renderer.pop(); + if (node.const_tags.length > 0) consequent = x`(() => { ${get_const_tags(node.const_tags)}; return ${consequent} })()`; renderer.push(); if (node.else) renderer.render(node.else.children, options); - const alternate = renderer.pop(); + let alternate: Node = renderer.pop(); + if (node.else && node.else.const_tags.length > 0) alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`; renderer.add_expression(x`${condition} ? ${consequent} : ${alternate}`); } diff --git a/test/runtime/samples/const-tag-each-else/_config.js b/test/runtime/samples/const-tag-each-else/_config.js new file mode 100644 index 00000000000..d95be09ccc6 --- /dev/null +++ b/test/runtime/samples/const-tag-each-else/_config.js @@ -0,0 +1,26 @@ +export default { + html: ` +
12 120 70, 30+4=34
+
35 350 120, 50+7=57
+
48 480 140, 60+8=68
+ `, + async test({ component, target, assert }) { + component.boxes = []; + assert.htmlEqual(target.innerHTML, ` +
10 * 2 = 20
+ `); + + component.constant = 35; + assert.htmlEqual(target.innerHTML, ` +
35 * 2 = 70
+ `); + + component.boxes = [ + {width: 3, height: 4} + ]; + + assert.htmlEqual(target.innerHTML, ` +
12 420 245, 105+4=109
+ `); + } +}; diff --git a/test/runtime/samples/const-tag-each-else/main.svelte b/test/runtime/samples/const-tag-each-else/main.svelte new file mode 100644 index 00000000000..7701a579cfb --- /dev/null +++ b/test/runtime/samples/const-tag-each-else/main.svelte @@ -0,0 +1,22 @@ + + +{#each boxes as box} + {@const {area, volume} = calculate(box.width, box.height, constant)} + {@const perimeter = (box.width + box.height) * constant} + {@const [width, height, sum] = [box.width * constant, box.height, box.width * constant + box.height]} +
{area} {volume} {perimeter}, {width}+{height}={sum}
+{:else} + {@const double = constant + constant} +
{constant} * 2 = {double}
+{/each} \ No newline at end of file diff --git a/test/runtime/samples/const-tag-if-else-if/_config.js b/test/runtime/samples/const-tag-if-else-if/_config.js new file mode 100644 index 00000000000..072eb2e44e6 --- /dev/null +++ b/test/runtime/samples/const-tag-if-else-if/_config.js @@ -0,0 +1,56 @@ +export default { + html: ` +
20 x 40
+
20 x 40
+ `, + props: { + boxes: [{ width: 20, height: 40 }] + }, + async test({ component, target, assert }) { + component.boxes = [{ width: 40, height: 70 }]; + assert.htmlEqual( + target.innerHTML, + ` +
40 x 70
+
40 x 70
+ ` + ); + + component.boxes = []; + + assert.htmlEqual(target.innerHTML, ''); + + component.boxes = [ + { width: 20, height: 40 }, + { width: 30, height: 50 } + ]; + + assert.htmlEqual( + target.innerHTML, + ` +
20 x 40
+
30 x 50
+
20 x 40
+
30 x 50
+ ` + ); + + component.boxes = [ + { width: 80, height: 70 }, + { width: 90, height: 60 } + ]; + + assert.htmlEqual( + target.innerHTML, + ` +
80 x 70
+
90 x 60
+
80 x 70
+
90 x 60
+ ` + ); + + component.boxes = []; + assert.htmlEqual(target.innerHTML, ''); + } +}; diff --git a/test/runtime/samples/const-tag-if-else-if/main.svelte b/test/runtime/samples/const-tag-if-else-if/main.svelte new file mode 100644 index 00000000000..8188625897c --- /dev/null +++ b/test/runtime/samples/const-tag-if-else-if/main.svelte @@ -0,0 +1,24 @@ + + +{#if boxes.length > 1} + {@const box1 = boxes[0]} + {@const box2 = boxes[1]} + {@const { width, height } = box1} +
{width} x {height}
+
{box2.width} x {box2.height}
+{:else if boxes.length > 0} + {@const box = boxes[0]} + {@const { width, height } = box} +
{width} x {height}
+{/if} + +{#if boxes.length > 1} +
{boxes[0].width} x {boxes[0].height}
+
{boxes[1].width} x {boxes[1].height}
+{:else if boxes.length > 0} + {@const box = boxes[0]} + {@const { width, height } = box} +
{width} x {height}
+{/if} \ No newline at end of file diff --git a/test/runtime/samples/const-tag-if-else-outro/_config.js b/test/runtime/samples/const-tag-if-else-outro/_config.js new file mode 100644 index 00000000000..56c7fc382b4 --- /dev/null +++ b/test/runtime/samples/const-tag-if-else-outro/_config.js @@ -0,0 +1,57 @@ +export default { + html: ` +
20 x 40
+
20 x 40
+ `, + props: { + boxes: [{ width: 20, height: 40 }] + }, + async test({ component, target, assert, raf }) { + component.boxes = [{ width: 40, height: 70 }]; + assert.htmlEqual( + target.innerHTML, + ` +
40 x 70
+
40 x 70
+ ` + ); + + component.boxes = []; + + raf.tick(0); + assert.htmlEqual(target.innerHTML, ''); + + component.boxes = [ + { width: 20, height: 40 }, + { width: 30, height: 50 } + ]; + + assert.htmlEqual( + target.innerHTML, + ` +
20 x 40
+
30 x 50
+
20 x 40
+
30 x 50
+ ` + ); + + component.boxes = [ + { width: 80, height: 70 }, + { width: 90, height: 60 } + ]; + + assert.htmlEqual( + target.innerHTML, + ` +
80 x 70
+
90 x 60
+
80 x 70
+
90 x 60
+ ` + ); + + component.boxes = []; + assert.htmlEqual(target.innerHTML, ''); + } +}; diff --git a/test/runtime/samples/const-tag-if-else-outro/main.svelte b/test/runtime/samples/const-tag-if-else-outro/main.svelte new file mode 100644 index 00000000000..90ac27fec17 --- /dev/null +++ b/test/runtime/samples/const-tag-if-else-outro/main.svelte @@ -0,0 +1,29 @@ + + +{#if boxes.length > 1} + {@const box1 = boxes[0]} + {@const box2 = boxes[1]} + {@const { width, height } = box1} +
{width} x {height}
+
{box2.width} x {box2.height}
+{:else if boxes.length > 0} + {@const box = boxes[0]} + {@const { width, height } = box} +
{width} x {height}
+{/if} + +{#if boxes.length > 1} +
{boxes[0].width} x {boxes[0].height}
+
{boxes[1].width} x {boxes[1].height}
+{:else if boxes.length > 0} + {@const box = boxes[0]} + {@const { width, height } = box} +
{width} x {height}
+{/if} \ No newline at end of file diff --git a/test/runtime/samples/const-tag-if-else/_config.js b/test/runtime/samples/const-tag-if-else/_config.js new file mode 100644 index 00000000000..13e59bf8363 --- /dev/null +++ b/test/runtime/samples/const-tag-if-else/_config.js @@ -0,0 +1,43 @@ +export default { + html: '
20 x 40
', + props: { + boxes: [{ width: 20, height: 40 }] + }, + async test({ component, target, assert }) { + component.boxes = [{ width: 30, height: 60 }]; + + assert.htmlEqual(target.innerHTML, ` +
30 x 60
+ `); + + component.boxes = [ + { width: 20, height: 40 }, + { width: 30, height: 50 } + ]; + + assert.htmlEqual(target.innerHTML, ` +
20 x 40
+
30 x 50
+ `); + + component.boxes = [ + { width: 80, height: 70 }, + { width: 90, height: 60 } + ]; + + assert.htmlEqual(target.innerHTML, ` +
80 x 70
+
90 x 60
+ `); + + component.boxes = [ + { width: 20, height: 40 }, + { width: 30, height: 50 }, + { width: 30, height: 50 } + ]; + assert.htmlEqual(target.innerHTML, '
3
'); + + component.boxes = []; + assert.htmlEqual(target.innerHTML, '
0
'); + } +}; diff --git a/test/runtime/samples/const-tag-if-else/main.svelte b/test/runtime/samples/const-tag-if-else/main.svelte new file mode 100644 index 00000000000..1da3d4ea663 --- /dev/null +++ b/test/runtime/samples/const-tag-if-else/main.svelte @@ -0,0 +1,18 @@ + + +{#if boxes.length === 2} + {@const box1 = boxes[0]} + {@const box2 = boxes[1]} + {@const { width, height } = box1} +
{width} x {height}
+
{box2.width} x {box2.height}
+{:else if boxes.length === 1} + {@const box = boxes[0]} + {@const { width, height } = box} +
{width} x {height}
+{:else} + {@const length = boxes.length} +
{length}
+{/if} diff --git a/test/runtime/samples/const-tag-if/_config.js b/test/runtime/samples/const-tag-if/_config.js new file mode 100644 index 00000000000..019584d016c --- /dev/null +++ b/test/runtime/samples/const-tag-if/_config.js @@ -0,0 +1,28 @@ +export default { + html: '
10 x 34
', + props: { + boxes: [{ width: 10, height: 34 }] + }, + async test({ component, target, assert }) { + component.boxes = [{ width: 20, height: 40 }]; + + assert.htmlEqual( + target.innerHTML, + ` +
20 x 40
+ ` + ); + + component.boxes = []; + assert.htmlEqual(target.innerHTML, ''); + + component.boxes = [{ width: 18, height: 48 }]; + + assert.htmlEqual( + target.innerHTML, + ` +
18 x 48
+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-if/main.svelte b/test/runtime/samples/const-tag-if/main.svelte new file mode 100644 index 00000000000..8f10ceefb8b --- /dev/null +++ b/test/runtime/samples/const-tag-if/main.svelte @@ -0,0 +1,9 @@ + + +{#if boxes.length > 0} + {@const box = boxes[0]} + {@const { width, height } = box} +
{width} x {height}
+{/if} diff --git a/test/validator/samples/const-tag-placement-1/errors.json b/test/validator/samples/const-tag-placement-1/errors.json index 52de84e2be8..5291b3a3511 100644 --- a/test/validator/samples/const-tag-placement-1/errors.json +++ b/test/validator/samples/const-tag-placement-1/errors.json @@ -1,7 +1,7 @@ [ { "code": "invalid-const-placement", - "message": "{@const} must be the immediate child of {#each}, {:then}, {:catch}, or ", + "message": "{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or ", "start": { "line": 5, "column": 0, "character": 36 }, "end": { "line": 5, "column": 18, "character": 54 }, "pos": 36 diff --git a/test/validator/samples/const-tag-placement-2/errors.json b/test/validator/samples/const-tag-placement-2/errors.json index 005b3ff8b74..9922063a1d3 100644 --- a/test/validator/samples/const-tag-placement-2/errors.json +++ b/test/validator/samples/const-tag-placement-2/errors.json @@ -1,9 +1,9 @@ [ { "code": "invalid-const-placement", - "message": "{@const} must be the immediate child of {#each}, {:then}, {:catch}, or ", - "start": { "line": 6, "column": 2, "character": 46 }, - "end": { "line": 6, "column": 20, "character": 64 }, - "pos": 46 + "message": "{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, or ", + "start": { "line": 7, "column": 4, "character": 63 }, + "end": { "line": 7, "column": 18, "character": 77 }, + "pos": 63 } ] diff --git a/test/validator/samples/const-tag-placement-2/input.svelte b/test/validator/samples/const-tag-placement-2/input.svelte index a843f9d58fb..2519482a987 100644 --- a/test/validator/samples/const-tag-placement-2/input.svelte +++ b/test/validator/samples/const-tag-placement-2/input.svelte @@ -2,6 +2,8 @@ export let a; -{#if a} - {@const b = a + 1} -{/if} \ No newline at end of file +{#each a as i} +
+ {@const b = i} +
+{/each} \ No newline at end of file diff --git a/test/validator/samples/const-tag-placement-3/errors.json b/test/validator/samples/const-tag-placement-3/errors.json deleted file mode 100644 index cc0ba4d74df..00000000000 --- a/test/validator/samples/const-tag-placement-3/errors.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "code": "invalid-const-placement", - "message": "{@const} must be the immediate child of {#each}, {:then}, {:catch}, or ", - "start": { "line": 7, "column": 4, "character": 63 }, - "end": { "line": 7, "column": 18, "character": 77 }, - "pos": 63 - } -] diff --git a/test/validator/samples/const-tag-placement-3/input.svelte b/test/validator/samples/const-tag-placement-3/input.svelte deleted file mode 100644 index 2519482a987..00000000000 --- a/test/validator/samples/const-tag-placement-3/input.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - -{#each a as i} -
- {@const b = i} -
-{/each} \ No newline at end of file