Date: Tue, 27 Jul 2021 21:53:55 +0800
Subject: [PATCH 65/69] [feat] support destructured declaration of props
(#6578)
---
src/compiler/compile/Component.ts | 159 ++++++++++++------
src/compiler/compile/compiler_errors.ts | 4 -
.../samples/destructured-props-1/A.svelte | 24 +++
.../samples/destructured-props-1/_config.js | 9 +
.../samples/destructured-props-1/main.svelte | 7 +
.../samples/destructured-props-2/A.svelte | 21 +++
.../samples/destructured-props-2/_config.js | 19 +++
.../samples/destructured-props-2/main.svelte | 30 ++++
.../samples/destructured-props-3/A.svelte | 10 ++
.../samples/destructured-props-3/_config.js | 15 ++
.../samples/destructured-props-3/main.svelte | 29 ++++
11 files changed, 271 insertions(+), 56 deletions(-)
create mode 100644 test/runtime/samples/destructured-props-1/A.svelte
create mode 100644 test/runtime/samples/destructured-props-1/_config.js
create mode 100644 test/runtime/samples/destructured-props-1/main.svelte
create mode 100644 test/runtime/samples/destructured-props-2/A.svelte
create mode 100644 test/runtime/samples/destructured-props-2/_config.js
create mode 100644 test/runtime/samples/destructured-props-2/main.svelte
create mode 100644 test/runtime/samples/destructured-props-3/A.svelte
create mode 100644 test/runtime/samples/destructured-props-3/_config.js
create mode 100644 test/runtime/samples/destructured-props-3/main.svelte
diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
index fa5a8d555e..6a8d5b178e 100644
--- a/src/compiler/compile/Component.ts
+++ b/src/compiler/compile/Component.ts
@@ -24,10 +24,10 @@ import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object';
import Slot from './nodes/Slot';
-import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal, ExportDefaultDeclaration, ExportAllDeclaration } from 'estree';
+import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, ExpressionStatement, AssignmentExpression, Literal, Property, RestElement, ExportDefaultDeclaration, ExportAllDeclaration } from 'estree';
import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
-import { print, x, b } from 'code-red';
+import { print, b } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords';
import { apply_preprocessor_sourcemap } from '../utils/mapped_code';
import Element from './nodes/Element';
@@ -943,7 +943,7 @@ export default class Component {
let scope = instance_scope;
walk(this.ast.instance.content, {
- enter(node: Node, parent, key, index) {
+ enter(node: Node) {
if (/Function/.test(node.type)) {
return this.skip();
}
@@ -952,75 +952,130 @@ export default class Component {
scope = map.get(node);
}
+ if (node.type === 'ExportNamedDeclaration' && node.declaration) {
+ return this.replace(node.declaration);
+ }
+
if (node.type === 'VariableDeclaration') {
+ // NOTE: `var` does not follow block scoping
if (node.kind === 'var' || scope === instance_scope) {
- node.declarations.forEach(declarator => {
- if (declarator.id.type !== 'Identifier') {
- const inserts = [];
-
- extract_names(declarator.id).forEach(name => {
- const variable = component.var_lookup.get(name);
-
- if (variable.export_name) {
- // TODO is this still true post-#3539?
- return component.error(declarator as any, compiler_errors.destructured_prop);
+ const inserts = [];
+ const props = [];
+
+ function add_new_props(exported, local, default_value) {
+ props.push({
+ type: 'Property',
+ method: false,
+ shorthand: false,
+ computed: false,
+ kind: 'init',
+ key: exported,
+ value: default_value
+ ? {
+ type: 'AssignmentPattern',
+ left: local,
+ right: default_value
}
+ : local
+ });
+ }
+ // transform
+ // ```
+ // export let { x, y = 123 } = OBJ, z = 456
+ // ```
+ // into
+ // ```
+ // let { x: x$, y: y$ = 123 } = OBJ;
+ // let { x = x$, y = y$, z = 456 } = $$props;
+ // ```
+ for (let index = 0; index < node.declarations.length; index++) {
+ const declarator = node.declarations[index];
+ if (declarator.id.type !== 'Identifier') {
+ function get_new_name(local) {
+ const variable = component.var_lookup.get(local.name);
if (variable.subscribable) {
inserts.push(get_insert(variable));
}
- });
- if (inserts.length) {
- parent[key].splice(index + 1, 0, ...inserts);
+ if (variable.export_name && variable.writable) {
+ const alias_name = component.get_unique_name(local.name);
+ add_new_props({ type: 'Identifier', name: variable.export_name }, local, alias_name);
+ return alias_name;
+ }
+ return local;
}
- return;
- }
-
- const { name } = declarator.id;
- const variable = component.var_lookup.get(name);
+ function rename_identifiers(param: Node) {
+ switch (param.type) {
+ case 'ObjectPattern': {
+ const handle_prop = (prop: Property | RestElement) => {
+ if (prop.type === 'RestElement') {
+ rename_identifiers(prop);
+ } else if (prop.value.type === 'Identifier') {
+ prop.value = get_new_name(prop.value);
+ } else {
+ rename_identifiers(prop.value);
+ }
+ };
+
+ param.properties.forEach(handle_prop);
+ break;
+ }
+ case 'ArrayPattern': {
+ const handle_element = (element: Node, index: number, array: Node[]) => {
+ if (element) {
+ if (element.type === 'Identifier') {
+ array[index] = get_new_name(element);
+ } else {
+ rename_identifiers(element);
+ }
+ }
+ };
+
+ param.elements.forEach(handle_element);
+ break;
+ }
+
+ case 'RestElement':
+ param.argument = get_new_name(param.argument);
+ break;
+
+ case 'AssignmentPattern':
+ param.left = get_new_name(param.left);
+ break;
+ }
+ }
- if (variable.export_name && variable.writable) {
- declarator.id = {
- type: 'ObjectPattern',
- properties: [{
- type: 'Property',
- method: false,
- shorthand: false,
- computed: false,
- kind: 'init',
- key: { type: 'Identifier', name: variable.export_name },
- value: declarator.init
- ? {
- type: 'AssignmentPattern',
- left: declarator.id,
- right: declarator.init
- }
- : declarator.id
- }]
- };
-
- declarator.init = x`$$props`;
+ rename_identifiers(declarator.id);
+ } else {
+ const { name } = declarator.id;
+ const variable = component.var_lookup.get(name);
+ const is_props = variable.export_name && variable.writable;
+ if (is_props) {
+ add_new_props({ type: 'Identifier', name: variable.export_name }, declarator.id, declarator.init);
+ node.declarations.splice(index--, 1);
+ }
+ if (variable.subscribable && (is_props || declarator.init)) {
+ inserts.push(get_insert(variable));
+ }
}
+ }
- if (variable.subscribable && declarator.init) {
- const insert = get_insert(variable);
- parent[key].splice(index + 1, 0, ...insert);
- }
- });
+ this.replace(b`
+ ${node.declarations.length ? node : null}
+ ${ props.length > 0 && b`let { ${ props } } = $$props;`}
+ ${inserts}
+ ` as any);
+ return this.skip();
}
}
},
- leave(node: Node, parent, _key, index) {
+ leave(node: Node) {
if (map.has(node)) {
scope = scope.parent;
}
-
- if (node.type === 'ExportNamedDeclaration' && node.declaration) {
- (parent as Program).body[index] = node.declaration;
- }
}
});
}
diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts
index fac7029c80..54263c3eb9 100644
--- a/src/compiler/compile/compiler_errors.ts
+++ b/src/compiler/compile/compiler_errors.ts
@@ -186,10 +186,6 @@ export default {
code: 'illegal-global',
message: `${name} is an illegal variable name`
}),
- destructured_prop: {
- code: 'destructured-prop',
- message: 'Cannot declare props in destructured declaration'
- },
cyclical_reactive_declaration: (cycle: string[]) => ({
code: 'cyclical-reactive-declaration',
message: `Cyclical dependency detected: ${cycle.join(' → ')}`
diff --git a/test/runtime/samples/destructured-props-1/A.svelte b/test/runtime/samples/destructured-props-1/A.svelte
new file mode 100644
index 0000000000..d1b392ef34
--- /dev/null
+++ b/test/runtime/samples/destructured-props-1/A.svelte
@@ -0,0 +1,24 @@
+
+
+
+a: {a},
+b: {typeof b},
+c: {c},
+d_one: {d_one},
+d_three: {$d_three},
+f: {f},
+g: {g},
+e: {typeof e},
+e_one: {e_one},
+A: {A},
+C: {C}
+
+{JSON.stringify(THING)}
\ No newline at end of file
diff --git a/test/runtime/samples/destructured-props-1/_config.js b/test/runtime/samples/destructured-props-1/_config.js
new file mode 100644
index 0000000000..29feaaabab
--- /dev/null
+++ b/test/runtime/samples/destructured-props-1/_config.js
@@ -0,0 +1,9 @@
+export default {
+ html: `
+ a: 1, b: undefined, c: 2, d_one: 3, d_three: 5, f: undefined, g: 9, e: undefined, e_one: 6, A: 1, C: 2
+ {"a":1,"b":{"c":2,"d":[3,4,{}]},"e":[6],"h":8}
+
+ a: a, b: undefined, c: 2, d_one: d_one, d_three: 5, f: f, g: g, e: undefined, e_one: 6, A: 1, C: 2
+ {"a":1,"b":{"c":2,"d":[3,4,{}]},"e":[6],"h":8}
+ `
+};
diff --git a/test/runtime/samples/destructured-props-1/main.svelte b/test/runtime/samples/destructured-props-1/main.svelte
new file mode 100644
index 0000000000..dbe7c88d33
--- /dev/null
+++ b/test/runtime/samples/destructured-props-1/main.svelte
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/test/runtime/samples/destructured-props-2/A.svelte b/test/runtime/samples/destructured-props-2/A.svelte
new file mode 100644
index 0000000000..51167edbc4
--- /dev/null
+++ b/test/runtime/samples/destructured-props-2/A.svelte
@@ -0,0 +1,21 @@
+
+
+
+ x: {x},
+ list_two_a: {list_two_a},
+ list_two_b: {list_two_b},
+ y: {$y},
+ m: {m},
+ n: {n},
+ o: {o},
+ p: {p},
+ q: {$q}
+
+{JSON.stringify(LIST)}
\ No newline at end of file
diff --git a/test/runtime/samples/destructured-props-2/_config.js b/test/runtime/samples/destructured-props-2/_config.js
new file mode 100644
index 0000000000..c647f31be1
--- /dev/null
+++ b/test/runtime/samples/destructured-props-2/_config.js
@@ -0,0 +1,19 @@
+export default {
+ html: `
+ x: 1, list_two_a: 2, list_two_b: 5, y: 4, m: 1, n: 2, o: 5, p: 3, q: 4
+ [1,{"a":2},[3,{}]]
+
x: 1, list_two_a: 2, list_two_b: 5, y: 4, m: m, n: n, o: o, p: p, q: q
+ [1,{"a":2},[3,{}]]
+ `,
+
+ async test({ component, assert, target }) {
+ await component.update();
+
+ assert.htmlEqual(target.innerHTML, `
+ x: 1, list_two_a: 2, list_two_b: 5, y: 4, m: 1, n: 2, o: 5, p: 3, q: 4
+ [1,{"a":2},[3,{}]]
+
x: 1, list_two_a: 2, list_two_b: 5, y: 4, m: MM, n: NN, o: OO, p: PP, q: QQ
+ [1,{"a":2},[3,{}]]
+ `);
+ }
+};
diff --git a/test/runtime/samples/destructured-props-2/main.svelte b/test/runtime/samples/destructured-props-2/main.svelte
new file mode 100644
index 0000000000..f0044bb567
--- /dev/null
+++ b/test/runtime/samples/destructured-props-2/main.svelte
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/test/runtime/samples/destructured-props-3/A.svelte b/test/runtime/samples/destructured-props-3/A.svelte
new file mode 100644
index 0000000000..6c5aca05b0
--- /dev/null
+++ b/test/runtime/samples/destructured-props-3/A.svelte
@@ -0,0 +1,10 @@
+
+
+i: {i}, j: {j}, k: {$k}, l: {l}, m: {m}, n: {$n}, a: {a}, b: {b}, c: {$c}, d: {d}, e: {e}, f: {$f}
\ No newline at end of file
diff --git a/test/runtime/samples/destructured-props-3/_config.js b/test/runtime/samples/destructured-props-3/_config.js
new file mode 100644
index 0000000000..6d2e516e0e
--- /dev/null
+++ b/test/runtime/samples/destructured-props-3/_config.js
@@ -0,0 +1,15 @@
+export default {
+ html: `
+ i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, a: 9, b: 10, c: 11, d: 12, e: 13, f: 14
+
+ i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, a: a, b: 10, c: c, d: d, e: 13, f: f
+ `,
+ async test({ component, target, assert }) {
+ await component.update();
+ assert.htmlEqual(target.innerHTML, `
+ i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, a: 9, b: 10, c: 11, d: 12, e: 13, f: 14
+
+ i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, a: aa, b: 10, c: cc, d: dd, e: 13, f: ff
+ `);
+ }
+};
diff --git a/test/runtime/samples/destructured-props-3/main.svelte b/test/runtime/samples/destructured-props-3/main.svelte
new file mode 100644
index 0000000000..8152d61aab
--- /dev/null
+++ b/test/runtime/samples/destructured-props-3/main.svelte
@@ -0,0 +1,29 @@
+
+
+
+
+
From 32b376b472c5fdd9e0de97c9c2a435e6c94af54f Mon Sep 17 00:00:00 2001
From: Conduitry
Date: Tue, 27 Jul 2021 09:54:54 -0400
Subject: [PATCH 66/69] update changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index effa1e821c..703bfac047 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## Unreleased
* Support `export { ... } from` syntax in components ([#2214](https://github.com/sveltejs/svelte/issues/2214))
+* Support `export let { ... } =` syntax in components ([#5612](https://github.com/sveltejs/svelte/issues/5612))
## 3.40.3
From e1d0d00ebb9a926822bb413f042b7784662b2355 Mon Sep 17 00:00:00 2001
From: Tan Li Hau
Date: Tue, 27 Jul 2021 21:58:32 +0800
Subject: [PATCH 67/69] [feat] allow shorthand {#await ... then/catch} (#6564)
---
src/compiler/parse/state/mustache.ts | 20 +++++--
.../await-catch-no-expression/_config.js | 47 ++++++++++++++++
.../await-catch-no-expression/main.svelte | 15 +++++
.../await-then-no-expression/_config.js | 55 +++++++++++++++++++
.../await-then-no-expression/main.svelte | 23 ++++++++
5 files changed, 154 insertions(+), 6 deletions(-)
create mode 100644 test/runtime/samples/await-catch-no-expression/_config.js
create mode 100644 test/runtime/samples/await-catch-no-expression/main.svelte
create mode 100644 test/runtime/samples/await-then-no-expression/_config.js
create mode 100644 test/runtime/samples/await-then-no-expression/main.svelte
diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts
index cc3c24349c..584f9d6e9a 100644
--- a/src/compiler/parse/state/mustache.ts
+++ b/src/compiler/parse/state/mustache.ts
@@ -290,16 +290,24 @@ export default function mustache(parser: Parser) {
const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
if (await_block_shorthand) {
- parser.require_whitespace();
- block.value = read_context(parser);
- parser.allow_whitespace();
+ if (parser.match_regex(/\s*}/)) {
+ parser.allow_whitespace();
+ } else {
+ parser.require_whitespace();
+ block.value = read_context(parser);
+ parser.allow_whitespace();
+ }
}
const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
if (await_block_catch_shorthand) {
- parser.require_whitespace();
- block.error = read_context(parser);
- parser.allow_whitespace();
+ if (parser.match_regex(/\s*}/)) {
+ parser.allow_whitespace();
+ } else {
+ parser.require_whitespace();
+ block.error = read_context(parser);
+ parser.allow_whitespace();
+ }
}
parser.eat('}', true);
diff --git a/test/runtime/samples/await-catch-no-expression/_config.js b/test/runtime/samples/await-catch-no-expression/_config.js
new file mode 100644
index 0000000000..5274e60e2d
--- /dev/null
+++ b/test/runtime/samples/await-catch-no-expression/_config.js
@@ -0,0 +1,47 @@
+let fulfil;
+
+let thePromise = new Promise(f => {
+ fulfil = f;
+});
+
+export default {
+ props: {
+ thePromise
+ },
+
+ html: `
+
+ the promise is pending
+ `,
+
+ async test({ assert, component, target }) {
+ fulfil(42);
+
+ await thePromise;
+
+ assert.htmlEqual(target.innerHTML, '
');
+
+ let reject;
+
+ thePromise = new Promise((f, r) => {
+ reject = r;
+ });
+
+ component.thePromise = thePromise;
+
+ assert.htmlEqual(target.innerHTML, `
+
+ the promise is pending
+ `);
+
+ reject(new Error());
+
+ await thePromise.catch(() => {});
+
+ assert.htmlEqual(target.innerHTML, `
+ oh no! Something broke!
+
+ oh no! Something broke!
+ `);
+ }
+};
diff --git a/test/runtime/samples/await-catch-no-expression/main.svelte b/test/runtime/samples/await-catch-no-expression/main.svelte
new file mode 100644
index 0000000000..0da0d12092
--- /dev/null
+++ b/test/runtime/samples/await-catch-no-expression/main.svelte
@@ -0,0 +1,15 @@
+
+
+{#await thePromise catch}
+ oh no! Something broke!
+{/await}
+
+
+
+{#await thePromise}
+ the promise is pending
+{:catch}
+ oh no! Something broke!
+{/await}
diff --git a/test/runtime/samples/await-then-no-expression/_config.js b/test/runtime/samples/await-then-no-expression/_config.js
new file mode 100644
index 0000000000..f684da52ed
--- /dev/null
+++ b/test/runtime/samples/await-then-no-expression/_config.js
@@ -0,0 +1,55 @@
+let fulfil;
+
+let thePromise = new Promise(f => {
+ fulfil = f;
+});
+
+export default {
+ props: {
+ thePromise
+ },
+
+ html: `
+
+
+ the promise is pending
+ `,
+
+ async test({ assert, component, target }) {
+ fulfil();
+
+ await thePromise;
+
+ assert.htmlEqual(target.innerHTML, `
+ the promise is resolved
+
+ the promise is resolved
+
+ the promise is resolved
+ `);
+
+ let reject;
+
+ thePromise = new Promise((f, r) => {
+ reject = r;
+ });
+
+ component.thePromise = thePromise;
+
+ assert.htmlEqual(target.innerHTML, `
+
+
+ the promise is pending
+ `);
+
+ reject(new Error('something broke'));
+
+ await thePromise.catch(() => {});
+
+ assert.htmlEqual(target.innerHTML, `
+ oh no! something broke
+
+
+ `);
+ }
+};
diff --git a/test/runtime/samples/await-then-no-expression/main.svelte b/test/runtime/samples/await-then-no-expression/main.svelte
new file mode 100644
index 0000000000..fedc7cd2b7
--- /dev/null
+++ b/test/runtime/samples/await-then-no-expression/main.svelte
@@ -0,0 +1,23 @@
+
+
+{#await thePromise then}
+ the promise is resolved
+{:catch theError}
+ oh no! {theError.message}
+{/await}
+
+
+
+{#await thePromise then}
+ the promise is resolved
+{/await}
+
+
+
+{#await thePromise}
+ the promise is pending
+{:then}
+ the promise is resolved
+{/await}
\ No newline at end of file
From 95b40f2492f2f44fc043a389a7a43b0653544f47 Mon Sep 17 00:00:00 2001
From: Conduitry
Date: Tue, 27 Jul 2021 09:59:40 -0400
Subject: [PATCH 68/69] update changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 703bfac047..91a2b55765 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
* Support `export { ... } from` syntax in components ([#2214](https://github.com/sveltejs/svelte/issues/2214))
* Support `export let { ... } =` syntax in components ([#5612](https://github.com/sveltejs/svelte/issues/5612))
+* Support `{#await ... then/catch}` without a variable for the resolved/rejected value ([#6270](https://github.com/sveltejs/svelte/issues/6270))
## 3.40.3
From ff6ce725bfdabba908690f4bae0b06a3f26da881 Mon Sep 17 00:00:00 2001
From: Conduitry
Date: Tue, 27 Jul 2021 10:01:49 -0400
Subject: [PATCH 69/69] -> v3.41.0
---
CHANGELOG.md | 2 +-
package-lock.json | 2 +-
package.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 91a2b55765..dadb5cd406 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
# Svelte changelog
-## Unreleased
+## 3.41.0
* Support `export { ... } from` syntax in components ([#2214](https://github.com/sveltejs/svelte/issues/2214))
* Support `export let { ... } =` syntax in components ([#5612](https://github.com/sveltejs/svelte/issues/5612))
diff --git a/package-lock.json b/package-lock.json
index e059174ea6..b4f16c00cc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.40.3",
+ "version": "3.41.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index 6f6303348f..0d39beb8f0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.40.3",
+ "version": "3.41.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",