diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts index a851bc24c2..d778ca1dbc 100644 --- a/src/compiler/compile/compiler_warnings.ts +++ b/src/compiler/compile/compiler_warnings.ts @@ -220,7 +220,7 @@ export default { }, invalid_rest_eachblock_binding: (rest_element_name: string) => ({ code: 'invalid-rest-eachblock-binding', - message: `...${rest_element_name} operator will create a new object and binding propagation with original object will not work` + message: `The rest operator (...) will create a new object and binding '${rest_element_name}' with the original object will not work` }), avoid_mouse_events_on_document: { code: 'avoid-mouse-events-on-document', diff --git a/src/compiler/compile/nodes/shared/Context.ts b/src/compiler/compile/nodes/shared/Context.ts index 76ac895681..43d4d1f626 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, PrivateIdentifier } from 'estree'; +import { Node, Identifier, Expression, PrivateIdentifier, Pattern } from 'estree'; import { walk } from 'estree-walker'; import is_reference, { NodeWithPropertyDefinition } from 'is-reference'; import { clone } from '../../../utils/clone'; @@ -30,15 +30,17 @@ export function unpack_destructuring({ default_modifier = (node) => node, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element = false }: { contexts: Context[]; - node: Node; + node: Pattern; modifier?: DestructuredVariable['modifier']; default_modifier?: DestructuredVariable['default_modifier']; scope: TemplateScope; component: Component; context_rest_properties: Map; + in_rest_element?: boolean; }) { if (!node) return; @@ -49,28 +51,26 @@ export function unpack_destructuring({ modifier, default_modifier }); - } else if (node.type === 'RestElement') { - contexts.push({ - type: 'DestructuredVariable', - key: node.argument as Identifier, - modifier, - default_modifier - }); - context_rest_properties.set((node.argument as Identifier).name, node); + + if (in_rest_element) { + context_rest_properties.set(node.name, node); + } } else if (node.type === 'ArrayPattern') { - node.elements.forEach((element, i) => { - if (element && element.type === 'RestElement') { + node.elements.forEach((element: Pattern | null, i: number) => { + if (!element) { + return; + } else if (element.type === 'RestElement') { unpack_destructuring({ contexts, - node: element, + node: element.argument, modifier: (node) => x`${modifier(node)}.slice(${i})` as Node, default_modifier, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element: true }); - context_rest_properties.set((element.argument as Identifier).name, element); - } else if (element && element.type === 'AssignmentPattern') { + } else if (element.type === 'AssignmentPattern') { const n = contexts.length; mark_referenced(element.right, scope, component); @@ -87,7 +87,8 @@ export function unpack_destructuring({ )}` as Node, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element }); } else { unpack_destructuring({ @@ -97,7 +98,8 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element }); } }); @@ -116,9 +118,9 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element: true }); - context_rest_properties.set((property.argument as Identifier).name, property); } else if (property.type === 'Property') { const key = property.key; const value = property.value; @@ -168,7 +170,8 @@ export function unpack_destructuring({ )}` as Node, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element }); } else { // e.g. { property } or { property: newName } @@ -179,7 +182,8 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element }); } } diff --git a/test/runtime/samples/await-then-destruct-array-nested-rest/_config.js b/test/runtime/samples/await-then-destruct-array-nested-rest/_config.js new file mode 100644 index 0000000000..9a287e35e2 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-array-nested-rest/_config.js @@ -0,0 +1,69 @@ +export default { + props: { + thePromise: new Promise(_ => {}) + }, + + html: ` + loading... + `, + + async test({ assert, component, target }) { + await (component.thePromise = Promise.resolve([1, 2, 3, 4, 5, 6, 7, 8])); + + assert.htmlEqual( + target.innerHTML, + ` +

a: 1

+

b: 2

+

c: 5

+

remaining length: 3

+ ` + ); + + await (component.thePromise = Promise.resolve([9, 10, 11, 12, 13, 14, 15])); + + assert.htmlEqual( + target.innerHTML, + ` +

a: 9

+

b: 10

+

c: 13

+

remaining length: 2

+ ` + ); + + try { + await (component.thePromise = Promise.reject([16, 17, 18, 19, 20, 21, 22])); + } catch (e) { + // do nothing + } + + assert.htmlEqual( + target.innerHTML, + ` +

c: 16

+

d: 17

+

e: 18

+

f: 19

+

g: 22

+ ` + ); + + try { + await (component.thePromise = Promise.reject([23, 24, 25, 26, 27, 28, 29, 30, 31])); + } catch (e) { + // do nothing + } + + assert.htmlEqual( + target.innerHTML, + ` +

c: 23

+

d: 24

+

e: 25

+

f: 26

+

g: 29

+ ` + ); + } +}; diff --git a/test/runtime/samples/await-then-destruct-array-nested-rest/main.svelte b/test/runtime/samples/await-then-destruct-array-nested-rest/main.svelte new file mode 100644 index 0000000000..4bb8ad0077 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-array-nested-rest/main.svelte @@ -0,0 +1,18 @@ + + +{#await thePromise} + loading... +{:then [ a, b, ...[,, c, ...{ length } ]]} +

a: {a}

+

b: {b}

+

c: {c}

+

remaining length: {length}

+{:catch [c, ...[d, e, f, ...[,,g]]]} +

c: {c}

+

d: {d}

+

e: {e}

+

f: {f}

+

g: {g}

+{/await} diff --git a/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/_config.js b/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/_config.js new file mode 100644 index 0000000000..cb40e7c456 --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/_config.js @@ -0,0 +1,19 @@ +export default { + html: '
12 120 70, 30+4=34
', + async test({ component, target, assert }) { + component.promise1 = Promise.resolve({width: 5, height: 6}); + component.promise2 = Promise.reject({width: 6, height: 7}); + + await Promise.resolve(); + assert.htmlEqual(target.innerHTML, ` +
30 300 110, 50+6=56
+
42 420 130, 60+7=67
+ `); + + component.constant = 20; + assert.htmlEqual(target.innerHTML, ` +
30 600 220, 100+6=106
+
42 840 260, 120+7=127
+ `); + } +}; diff --git a/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/main.svelte b/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/main.svelte new file mode 100644 index 0000000000..7af5c989e4 --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/main.svelte @@ -0,0 +1,23 @@ + + +{#await promise1 then { width, height }} + {@const {area, volume} = calculate(width, height, constant)} + {@const perimeter = (width + height) * constant} + {@const [_width, ...[_height, ...[sum]]] = [width * constant, height, width * constant + height]} +
{area} {volume} {perimeter}, {_width}+{_height}={sum}
+{/await} + +{#await promise2 catch { width, height }} + {@const {area, volume} = calculate(width, height, constant)} + {@const perimeter = (width + height) * constant} + {@const [_width, ...[_height, ...[sum]]] = [width * constant, height, width * constant + height]} +
{area} {volume} {perimeter}, {_width}+{_height}={sum}
+{/await} diff --git a/test/runtime/samples/const-tag-each-destructure-nested-rest/_config.js b/test/runtime/samples/const-tag-each-destructure-nested-rest/_config.js new file mode 100644 index 0000000000..00f8b31540 --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-nested-rest/_config.js @@ -0,0 +1,30 @@ +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.constant = 20; + + assert.htmlEqual(target.innerHTML, ` +
12 240 140, 60+4=64
+
35 700 240, 100+7=107
+
48 960 280, 120+8=128
+ `); + + component.boxes = [ + {width: 3, height: 4}, + {width: 4, height: 5}, + {width: 5, height: 6}, + {width: 6, height: 7} + ]; + + assert.htmlEqual(target.innerHTML, ` +
12 240 140, 60+4=64
+
20 400 180, 80+5=85
+
30 600 220, 100+6=106
+
42 840 260, 120+7=127
+ `); + } +}; diff --git a/test/runtime/samples/const-tag-each-destructure-nested-rest/main.svelte b/test/runtime/samples/const-tag-each-destructure-nested-rest/main.svelte new file mode 100644 index 0000000000..4361314b19 --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-nested-rest/main.svelte @@ -0,0 +1,19 @@ + + +{#each boxes as { width, height }} + {@const {area, volume} = calculate(width, height, constant)} + {@const perimeter = (width + height) * constant} + {@const [_width, ...[_height, ...[sum]]] = [width * constant, height, width * constant + height]} +
{area} {volume} {perimeter}, {_width}+{_height}={sum}
+{/each} diff --git a/test/runtime/samples/each-block-destructured-array-nested-rest/_config.js b/test/runtime/samples/each-block-destructured-array-nested-rest/_config.js new file mode 100644 index 0000000000..5922925956 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-nested-rest/_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, Second: 2, Third: 3, Elements remaining: 2

+

First: 6, Second: 7, Third: 8, Elements remaining: 0

+

First: 9, Second: 10, Third: 11, Elements remaining: 1

+

First: 13, Second: 14, Third: 15, Elements remaining: 7

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

First: 23, Second: 24, Third: 25, Elements remaining: 4

+ `); + } +}; diff --git a/test/runtime/samples/each-block-destructured-array-nested-rest/main.svelte b/test/runtime/samples/each-block-destructured-array-nested-rest/main.svelte new file mode 100644 index 0000000000..fc73462464 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-nested-rest/main.svelte @@ -0,0 +1,9 @@ + + +{#each array as [first, second, ...[third, ...{ length }]]} +

+ First: {first}, Second: {second}, Third: {third}, Elements remaining: {length} +

+{/each} diff --git a/test/validator/samples/rest-eachblock-binding-2/warnings.json b/test/validator/samples/rest-eachblock-binding-2/warnings.json index a471dd6a86..174982f21b 100644 --- a/test/validator/samples/rest-eachblock-binding-2/warnings.json +++ b/test/validator/samples/rest-eachblock-binding-2/warnings.json @@ -1,8 +1,8 @@ [ { "code": "invalid-rest-eachblock-binding", - "message": "...rest operator will create a new object and binding propagation with original object will not work", - "start": { "line": 8, "column": 24 }, + "message": "The rest operator (...) will create a new object and binding 'rest' with the original object will not work", + "start": { "line": 8, "column": 27 }, "end": { "line": 8, "column": 31 } } ] diff --git a/test/validator/samples/rest-eachblock-binding-3/warnings.json b/test/validator/samples/rest-eachblock-binding-3/warnings.json index eda7e1fc5d..3311d2afe4 100644 --- a/test/validator/samples/rest-eachblock-binding-3/warnings.json +++ b/test/validator/samples/rest-eachblock-binding-3/warnings.json @@ -1,8 +1,8 @@ [ { "code": "invalid-rest-eachblock-binding", - "message": "...rest operator will create a new object and binding propagation with original object will not work", - "start": { "line": 5, "column": 32 }, - "end": { "line": 5, "column": 39 } + "message": "The rest operator (...) will create a new object and binding 'rest' with the original object will not work", + "start": { "line": 5, "column": 35 }, + "end": { "line": 5, "column": 39 } } ] diff --git a/test/validator/samples/rest-eachblock-binding-nested-rest/input.svelte b/test/validator/samples/rest-eachblock-binding-nested-rest/input.svelte new file mode 100644 index 0000000000..31f32975f6 --- /dev/null +++ b/test/validator/samples/rest-eachblock-binding-nested-rest/input.svelte @@ -0,0 +1,9 @@ + + +{#each a as [first, second, ...[third, ...{ length }]]} +

{first}, {second}, {length}

+ + +{/each} diff --git a/test/validator/samples/rest-eachblock-binding-nested-rest/warnings.json b/test/validator/samples/rest-eachblock-binding-nested-rest/warnings.json new file mode 100644 index 0000000000..d935275edf --- /dev/null +++ b/test/validator/samples/rest-eachblock-binding-nested-rest/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "invalid-rest-eachblock-binding", + "end": { + "column": 37, + "line": 5 + }, + "message": "The rest operator (...) will create a new object and binding 'third' with the original object will not work", + "start": { + "column": 32, + "line": 5 + } + }, + { + "code": "invalid-rest-eachblock-binding", + "end": { + "column": 50, + "line": 5 + }, + "message": "The rest operator (...) will create a new object and binding 'length' with the original object will not work", + "start": { + "column": 44, + "line": 5 + } + } +] diff --git a/test/validator/samples/rest-eachblock-binding/warnings.json b/test/validator/samples/rest-eachblock-binding/warnings.json index 35fb2d0b6e..992e8880d9 100644 --- a/test/validator/samples/rest-eachblock-binding/warnings.json +++ b/test/validator/samples/rest-eachblock-binding/warnings.json @@ -1,8 +1,8 @@ [ { "code": "invalid-rest-eachblock-binding", - "message": "...rest operator will create a new object and binding propagation with original object will not work", - "start": { "line": 5, "column": 25 }, + "message": "The rest operator (...) will create a new object and binding 'rest' with the original object will not work", + "start": { "line": 5, "column": 28 }, "end": { "line": 5, "column": 32 } } ]