From a1e84213680d6716b304930d93f3133c4a49d43f Mon Sep 17 00:00:00 2001 From: Nguyen Tran <88808276+ngtr6788@users.noreply.github.com> Date: Wed, 15 Mar 2023 12:10:40 -0400 Subject: [PATCH] fix: object destructuring picks up computed properties (#8386) fixes #6609. Prior related PR: #8357 --- src/compiler/compile/nodes/CatchBlock.ts | 1 + src/compiler/compile/nodes/ConstTag.ts | 1 + src/compiler/compile/nodes/EachBlock.ts | 1 + src/compiler/compile/nodes/ThenBlock.ts | 1 + src/compiler/compile/nodes/shared/Context.ts | 69 ++++++++++++++----- .../compile/nodes/shared/Expression.ts | 1 + .../compile/render_dom/wrappers/AwaitBlock.ts | 12 +++- .../compile/render_dom/wrappers/EachBlock.ts | 13 +++- .../wrappers/shared/add_const_tags.ts | 39 +++++++---- .../_config.js | 32 +++++++++ .../main.svelte | 23 +++++++ .../_config.js | 20 ++++++ .../main.svelte | 25 +++++++ .../_config.js | 15 ++++ .../main.svelte | 42 +++++++++++ .../_config.js | 24 +++++++ .../main.svelte | 7 ++ .../_config.js | 26 +++++++ .../main.svelte | 12 ++++ 19 files changed, 330 insertions(+), 34 deletions(-) create mode 100644 test/runtime/samples/await-then-destruct-computed-props/_config.js create mode 100644 test/runtime/samples/await-then-destruct-computed-props/main.svelte create mode 100644 test/runtime/samples/const-tag-await-then-destructuring-computed-props/_config.js create mode 100644 test/runtime/samples/const-tag-await-then-destructuring-computed-props/main.svelte create mode 100644 test/runtime/samples/const-tag-each-destructure-computed-props/_config.js create mode 100644 test/runtime/samples/const-tag-each-destructure-computed-props/main.svelte create mode 100644 test/runtime/samples/each-block-destructured-array-computed-props/_config.js create mode 100644 test/runtime/samples/each-block-destructured-array-computed-props/main.svelte create mode 100644 test/runtime/samples/each-block-destructured-object-computed-props/_config.js create mode 100644 test/runtime/samples/each-block-destructured-object-computed-props/main.svelte diff --git a/src/compiler/compile/nodes/CatchBlock.ts b/src/compiler/compile/nodes/CatchBlock.ts index ba6a4b77a6..d92b4eda56 100644 --- a/src/compiler/compile/nodes/CatchBlock.ts +++ b/src/compiler/compile/nodes/CatchBlock.ts @@ -17,6 +17,7 @@ export default class CatchBlock extends AbstractBlock { this.scope = scope.child(); if (parent.catch_node) { parent.catch_contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; this.scope.add(context.key.name, parent.expression.dependencies, this); }); } diff --git a/src/compiler/compile/nodes/ConstTag.ts b/src/compiler/compile/nodes/ConstTag.ts index 44a50aa005..edcb949c15 100644 --- a/src/compiler/compile/nodes/ConstTag.ts +++ b/src/compiler/compile/nodes/ConstTag.ts @@ -65,6 +65,7 @@ export default class ConstTag extends Node { }); this.expression = new Expression(this.component, this, this.scope, this.node.expression.right); this.contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; const owner = this.scope.get_owner(context.key.name); if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) { this.component.error(this.node, compiler_errors.invalid_const_declaration(context.key.name)); diff --git a/src/compiler/compile/nodes/EachBlock.ts b/src/compiler/compile/nodes/EachBlock.ts index 4a5ea19e37..4659a53d73 100644 --- a/src/compiler/compile/nodes/EachBlock.ts +++ b/src/compiler/compile/nodes/EachBlock.ts @@ -45,6 +45,7 @@ export default class EachBlock extends AbstractBlock { unpack_destructuring({ contexts: this.contexts, node: info.context, scope, component, context_rest_properties: this.context_rest_properties }); this.contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; this.scope.add(context.key.name, this.expression.dependencies, this); }); diff --git a/src/compiler/compile/nodes/ThenBlock.ts b/src/compiler/compile/nodes/ThenBlock.ts index 6aee3f916f..b394db1e47 100644 --- a/src/compiler/compile/nodes/ThenBlock.ts +++ b/src/compiler/compile/nodes/ThenBlock.ts @@ -17,6 +17,7 @@ export default class ThenBlock extends AbstractBlock { this.scope = scope.child(); if (parent.then_node) { parent.then_contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; this.scope.add(context.key.name, parent.expression.dependencies, this); }); } diff --git a/src/compiler/compile/nodes/shared/Context.ts b/src/compiler/compile/nodes/shared/Context.ts index 5cef46482b..3957ed2173 100644 --- a/src/compiler/compile/nodes/shared/Context.ts +++ b/src/compiler/compile/nodes/shared/Context.ts @@ -1,5 +1,5 @@ import { x } from 'code-red'; -import { Node, Identifier, Expression } from 'estree'; +import { Node, Identifier, Expression, PrivateIdentifier } from 'estree'; import { walk } from 'estree-walker'; import is_reference, { NodeWithPropertyDefinition } from 'is-reference'; import { clone } from '../../../utils/clone'; @@ -7,7 +7,16 @@ import Component from '../../Component'; import flatten_reference from '../../utils/flatten_reference'; import TemplateScope from './TemplateScope'; -export interface Context { +export type Context = DestructuredVariable | ComputedProperty; + +interface ComputedProperty { + type: 'ComputedProperty'; + property_name: string; + key: Expression | PrivateIdentifier; +} + +interface DestructuredVariable { + type: 'DestructuredVariable' key: Identifier; name?: string; modifier: (node: Node) => Node; @@ -21,26 +30,33 @@ export function unpack_destructuring({ default_modifier = (node) => node, scope, component, - context_rest_properties + context_rest_properties, + number_of_computed_props = { n: 0 } }: { contexts: Context[]; node: Node; - modifier?: Context['modifier']; - default_modifier?: Context['default_modifier']; + modifier?: DestructuredVariable['modifier']; + default_modifier?: DestructuredVariable['default_modifier']; scope: TemplateScope; component: Component; context_rest_properties: Map; + // we want to pass this by reference, as a sort of global variable, because + // if we pass this by value, we could get computed_property_# variable collisions + // when we deal with nested object destructuring + number_of_computed_props?: { n: number }; }) { if (!node) return; if (node.type === 'Identifier') { contexts.push({ + type: 'DestructuredVariable', key: node as Identifier, modifier, default_modifier }); } else if (node.type === 'RestElement') { contexts.push({ + type: 'DestructuredVariable', key: node.argument as Identifier, modifier, default_modifier @@ -56,7 +72,8 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + number_of_computed_props }); context_rest_properties.set((element.argument as Identifier).name, element); } else if (element && element.type === 'AssignmentPattern') { @@ -76,7 +93,8 @@ export function unpack_destructuring({ )}` as Node, scope, component, - context_rest_properties + context_rest_properties, + number_of_computed_props }); } else { unpack_destructuring({ @@ -86,7 +104,8 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + number_of_computed_props }); } }); @@ -105,29 +124,41 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + number_of_computed_props }); context_rest_properties.set((property.argument as Identifier).name, property); } else if (property.type === 'Property') { const key = property.key; const value = property.value; - let property_name: any; let new_modifier: (node: Node) => Node; if (property.computed) { - // TODO: If the property is computed, ie, { [computed_key]: prop }, the computed_key can be any type of expression. + // e.g { [computedProperty]: ... } + const property_name = `computed_property_${number_of_computed_props.n}`; + number_of_computed_props.n += 1; + + contexts.push({ + type: 'ComputedProperty', + property_name, + key + }); + + new_modifier = (node) => x`${modifier(node)}[${property_name}]`; + used_properties.push(x`${property_name}`); } else if (key.type === 'Identifier') { // e.g. { someProperty: ... } - property_name = key.name; + const property_name = key.name; new_modifier = (node) => x`${modifier(node)}.${property_name}`; + used_properties.push(x`"${property_name}"`); } else if (key.type === 'Literal') { // e.g. { "property-in-quotes": ... } or { 14: ... } - property_name = key.value; + const property_name = key.value; new_modifier = (node) => x`${modifier(node)}["${property_name}"]`; + used_properties.push(x`"${property_name}"`); } - used_properties.push(x`"${property_name}"`); if (value.type === 'AssignmentPattern') { // e.g. { property = default } or { property: newName = default } const n = contexts.length; @@ -147,7 +178,8 @@ export function unpack_destructuring({ )}` as Node, scope, component, - context_rest_properties + context_rest_properties, + number_of_computed_props }); } else { // e.g. { property } or { property: newName } @@ -158,7 +190,8 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + number_of_computed_props }); } } @@ -174,7 +207,9 @@ function update_reference( ): Node { const find_from_context = (node: Identifier) => { for (let i = n; i < contexts.length; i++) { - const { key } = contexts[i]; + const cur_context = contexts[i]; + if (cur_context.type !== 'DestructuredVariable') continue; + const { key } = cur_context; if (node.name === key.name) { throw new Error(`Cannot access '${node.name}' before initialization`); } diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 1b1558ec17..3a85bf35c8 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -373,6 +373,7 @@ export default class Expression { // add to get_xxx_context // child_ctx[x] = function () { ... } (template_scope.get_owner(deps[0]) as EachBlock).contexts.push({ + type: 'DestructuredVariable', key: func_id, modifier: () => func_expression, default_modifier: node => node diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index 9c07267223..534595f4a9 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -11,6 +11,7 @@ import CatchBlock from '../../nodes/CatchBlock'; import { Context } from '../../nodes/shared/Context'; import { Identifier, Literal, Node } from 'estree'; import { add_const_tags, add_const_tags_context } from './shared/add_const_tags'; +import Expression from '../../nodes/shared/Expression'; type Status = 'pending' | 'then' | 'catch'; @@ -69,6 +70,7 @@ class AwaitBlockBranch extends Wrapper { this.renderer.add_to_context(this.value, true); } else { contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; this.renderer.add_to_context(context.key.name, true); }); this.value = this.block.parent.get_unique_name('value').name; @@ -96,7 +98,15 @@ class AwaitBlockBranch extends Wrapper { } render_get_context() { - const props = this.is_destructured ? this.value_contexts.map(prop => b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), name => this.renderer.reference(name))};`) : null; + const props = this.is_destructured ? this.value_contexts.map(prop => { + if (prop.type === 'ComputedProperty') { + const expression = new Expression(this.renderer.component, this.node, this.has_consts(this.node) ? this.node.scope : null, prop.key); + return b`const ${prop.property_name} = ${expression.manipulate(this.block, '#ctx')};`; + } else { + const to_ctx = name => this.renderer.reference(name); + return b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), to_ctx)};`; + } + }) : null; const const_tags_props = this.has_consts(this.node) ? add_const_tags(this.block, this.node.const_tags, '#ctx') : null; diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 1032f4c6e3..9c1af7ce44 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -9,6 +9,7 @@ import ElseBlock from '../../nodes/ElseBlock'; import { Identifier, Node } from 'estree'; import get_object from '../../utils/get_object'; import { add_const_tags, add_const_tags_context } from './shared/add_const_tags'; +import Expression from '../../nodes/shared/Expression'; export class ElseBlockWrapper extends Wrapper { node: ElseBlock; @@ -86,6 +87,7 @@ export default class EachBlockWrapper extends Wrapper { block.add_dependencies(dependencies); this.node.contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; renderer.add_to_context(context.key.name, true); }); add_const_tags_context(renderer, this.node.const_tags); @@ -147,6 +149,7 @@ export default class EachBlockWrapper extends Wrapper { const store = object.type === 'Identifier' && object.name[0] === '$' ? object.name.slice(1) : null; node.contexts.forEach(prop => { + if (prop.type !== 'DestructuredVariable') return; this.block.bindings.set(prop.key.name, { object: this.vars.each_block_value, property: this.index_name, @@ -361,7 +364,15 @@ export default class EachBlockWrapper extends Wrapper { this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier); } - this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`list[i]`), name => renderer.context_lookup.has(name) ? x`child_ctx[${renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name })};`); + this.context_props = this.node.contexts.map(prop => { + if (prop.type === 'DestructuredVariable') { + const to_ctx = (name: string) => renderer.context_lookup.has(name) ? x`child_ctx[${renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node; + return b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`list[i]`), to_ctx)};`; + } else { + const expression = new Expression(this.renderer.component, this.node, this.node.scope, prop.key); + return b`const ${prop.property_name} = ${expression.manipulate(block, 'child_ctx')};`; + } + }); if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`); if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`); diff --git a/src/compiler/compile/render_dom/wrappers/shared/add_const_tags.ts b/src/compiler/compile/render_dom/wrappers/shared/add_const_tags.ts index cd4dc7f033..b7c14c2c38 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/add_const_tags.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/add_const_tags.ts @@ -1,24 +1,33 @@ import ConstTag from '../../../nodes/ConstTag'; import Block from '../../Block'; -import { b, x } from 'code-red'; +import { b, Node, x } from 'code-red'; import Renderer from '../../Renderer'; +import Expression from '../../../nodes/shared/Expression'; export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string) { - const const_tags_props = []; - const_tags.forEach((const_tag, i) => { - const name = `#constants_${i}`; - const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`); - const_tag.contexts.forEach(context => { - const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), name => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name })};`); - }); - }); - return const_tags_props; + const const_tags_props = []; + const_tags.forEach((const_tag, i) => { + const name = `#constants_${i}`; + const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`); + const to_ctx = (name: string) => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node; + + const_tag.contexts.forEach(context => { + if (context.type === 'DestructuredVariable') { + const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), to_ctx)}`); + } else { + const expression = new Expression(block.renderer.component, const_tag, const_tag.scope, context.key); + const_tags_props.push(b`const ${context.property_name} = ${expression.manipulate(block, ctx)}`); + } + }); + }); + return const_tags_props; } export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) { - const_tags.forEach(const_tag => { - const_tag.contexts.forEach(context => { - renderer.add_to_context(context.key.name, true); - }); - }); + const_tags.forEach(const_tag => { + const_tag.contexts.forEach(context => { + if (context.type !== 'DestructuredVariable') return; + renderer.add_to_context(context.key.name, true); + }); + }); } diff --git a/test/runtime/samples/await-then-destruct-computed-props/_config.js b/test/runtime/samples/await-then-destruct-computed-props/_config.js new file mode 100644 index 0000000000..6d147535e3 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-computed-props/_config.js @@ -0,0 +1,32 @@ +export default { + async test({ assert, component, target }) { + await Promise.resolve(); + + assert.htmlEqual( + target.innerHTML, + ` +

propA: 3

+

propB: 7

+

num: 3

+

rest: {"prop3":{"prop9":9,"prop10":10}}

+

propZ: 5

+

propY: 6

+

rest: {"propX":7,"propW":8}

+ ` + ); + + await (component.object = Promise.resolve({ prop1: 'one', prop2: 'two', prop3: { prop7: 'seven' }, prop4: { prop10: 'ten' }})); + assert.htmlEqual( + target.innerHTML, + ` +

propA: seven

+

propB: ten

+

num: 5

+

rest: {"prop1":"one","prop2":"two"}

+

propZ: 5

+

propY: 6

+

rest: {"propX":7,"propW":8}

+ ` + ); + } +}; diff --git a/test/runtime/samples/await-then-destruct-computed-props/main.svelte b/test/runtime/samples/await-then-destruct-computed-props/main.svelte new file mode 100644 index 0000000000..71756d6cf3 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-computed-props/main.svelte @@ -0,0 +1,23 @@ + + +{#await object then { [`prop${num++}`]: { [`prop${num + 3}`]: propA }, [`prop${num++}`]: { [`prop${num + 5}`]: propB }, ...rest }} +

propA: {propA}

+

propB: {propB}

+

num: {num}

+

rest: {JSON.stringify(rest)}

+{/await} + +{#await objectReject then value} + resolved +{:catch { [`${prop}Z`]: propZ, [`${prop}Y`]: propY, ...rest }} +

propZ: {propZ}

+

propY: {propY}

+

rest: {JSON.stringify(rest)}

+{/await} + diff --git a/test/runtime/samples/const-tag-await-then-destructuring-computed-props/_config.js b/test/runtime/samples/const-tag-await-then-destructuring-computed-props/_config.js new file mode 100644 index 0000000000..9ec4a3e3e6 --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-computed-props/_config.js @@ -0,0 +1,20 @@ +export default { + html: ` +

4, 12, 60

+ `, + + async test({ component, target, assert }) { + component.permutation = [2, 3, 1]; + await (component.promise1 = Promise.resolve({length: 1, width: 2, height: 3})); + try { + await (component.promise2 = Promise.reject({length: 97, width: 98, height: 99})); + } catch (e) { + // nothing + } + + assert.htmlEqual(target.innerHTML, ` +

2, 11, 2

+

9506, 28811, 98

+ `); + } +}; diff --git a/test/runtime/samples/const-tag-await-then-destructuring-computed-props/main.svelte b/test/runtime/samples/const-tag-await-then-destructuring-computed-props/main.svelte new file mode 100644 index 0000000000..fbc54e4d0b --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-computed-props/main.svelte @@ -0,0 +1,25 @@ + + +{#await promise1 then { length, width, height }} + {@const [a, b, c] = permutation} + {@const { [`${a}-Dimensions`]: { [c - 1]: first }, [`${b}-Dimensions`]: { [b - 1]: second }, [`${c}-Dimensions`]: { [a - 1]: third } } = calculate(length, width, height) } +

{first}, {second}, {third}

+{/await} + +{#await promise2 catch { length, width, height }} + {@const [a, b, c] = permutation} + {@const { [`${a}-Dimensions`]: { [c - 1]: first }, [`${b}-Dimensions`]: { [b - 1]: second }, [`${c}-Dimensions`]: { [a - 1]: third } } = calculate(length, width, height) } +

{first}, {second}, {third}

+{/await} diff --git a/test/runtime/samples/const-tag-each-destructure-computed-props/_config.js b/test/runtime/samples/const-tag-each-destructure-computed-props/_config.js new file mode 100644 index 0000000000..c299f19da6 --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-computed-props/_config.js @@ -0,0 +1,15 @@ +export default { + html: ` + + + + `, + + async test({ component, target, assert }) { + component.boxes = [{ length: 10, width: 20, height: 30 }]; + + assert.htmlEqual(target.innerHTML, + '' + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-destructure-computed-props/main.svelte b/test/runtime/samples/const-tag-each-destructure-computed-props/main.svelte new file mode 100644 index 0000000000..c8c7e1977a --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-computed-props/main.svelte @@ -0,0 +1,42 @@ + + +{#each boxes as { length, width, height }} + {@const { + [`two${dimension}`]: { + i = 1, + [`bottom${area}`]: bottom, + [`side${area}${i++}`]: sideone, + [`side${area}${i++}`]: sidetwo + }, + [`three${dimension}`]: { + volume + } + } = calculate(length, width, height)} + +{/each} diff --git a/test/runtime/samples/each-block-destructured-array-computed-props/_config.js b/test/runtime/samples/each-block-destructured-array-computed-props/_config.js new file mode 100644 index 0000000000..e400e2f280 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-computed-props/_config.js @@ -0,0 +1,24 @@ +export default { + props: { + array: [ + [1, 2, 3, 4, 5], + [6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16, 17, 18, 19, 20, 21, 22] + ] + }, + + html: ` +

First: 1, Half: 3, Last: 5, Length: 5

+

First: 6, Half: 7, Last: 8, Length: 3

+

First: 9, Half: 11, Last: 12, Length: 4

+

First: 13, Half: 18, Last: 22, Length: 10

+ `, + + test({ assert, component, target }) { + component.array = [[23, 24, 25, 26, 27, 28, 29]]; + assert.htmlEqual( target.innerHTML, ` +

First: 23, Half: 26, Last: 29, Length: 7

+ `); + } +}; diff --git a/test/runtime/samples/each-block-destructured-array-computed-props/main.svelte b/test/runtime/samples/each-block-destructured-array-computed-props/main.svelte new file mode 100644 index 0000000000..00a9a99a89 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-computed-props/main.svelte @@ -0,0 +1,7 @@ + + +{#each array as { 0: first, length, [length - 1]: last, [Math.floor(length / 2)]: half }} +

First: {first}, Half: {half}, Last: {last}, Length: {length}

+{/each} diff --git a/test/runtime/samples/each-block-destructured-object-computed-props/_config.js b/test/runtime/samples/each-block-destructured-object-computed-props/_config.js new file mode 100644 index 0000000000..7e98fab913 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-computed-props/_config.js @@ -0,0 +1,26 @@ +export default { + props: { + firstString: 'cats', + secondString: 'dogs', + objectsArray: [ + { dogs: 'woof', cats: 'meow', stac: 'stack', DOGS: 'WOOF' }, + { dogs: 'A German sheppard', cats: 'A tailless cat', stac: 'A jenga tower', DOGS: 'A GERMAN SHEPPARD' }, + { dogs: 'dogs', cats: 'cats', stac: 'stac', DOGS: 'DOGS' } + ] + }, + + html: ` +

cats: meow

+

dogs: woof

+

stac: stack

+

DOGS: WOOF

+

cats: A tailless cat

+

dogs: A German sheppard

+

stac: A jenga tower

+

DOGS: A GERMAN SHEPPARD

+

cats: cats

+

dogs: dogs

+

stac: stac

+

DOGS: DOGS

+ ` +}; diff --git a/test/runtime/samples/each-block-destructured-object-computed-props/main.svelte b/test/runtime/samples/each-block-destructured-object-computed-props/main.svelte new file mode 100644 index 0000000000..9c2ae29617 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-computed-props/main.svelte @@ -0,0 +1,12 @@ + + +{#each objectsArray as { [firstString]: firstProp, [secondString]: secondProp, [firstString.split('').reverse().join('')]: reverseFirst, [secondString.toUpperCase()]: upperSecond } } +

{firstString}: {firstProp}

+

{secondString}: {secondProp}

+

{firstString.split('').reverse().join('')}: {reverseFirst}

+

{secondString.toUpperCase()}: {upperSecond}

+{/each}