fix: support destructurings containing await (#9962)

Adds a traversion mechanism to found out if destructured expressions contain await
Fixes #9686
Fixes #9312
Fixes #9982
pull/10127/head
Nguyen Tran 12 months ago committed by GitHub
parent d16f17c306
commit 1ff9c0f2b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: support async/await in destructuring assignments

@ -115,6 +115,103 @@ export function serialize_get_binding(node, state) {
return node;
}
/**
* @param {import('estree').Expression | import('estree').Pattern} expression
* @returns {boolean}
*/
function is_expression_async(expression) {
switch (expression.type) {
case 'AwaitExpression': {
return true;
}
case 'ArrayPattern': {
return expression.elements.some((element) => element && is_expression_async(element));
}
case 'ArrayExpression': {
return expression.elements.some((element) => {
if (!element) {
return false;
} else if (element.type === 'SpreadElement') {
return is_expression_async(element.argument);
} else {
return is_expression_async(element);
}
});
}
case 'AssignmentPattern':
case 'AssignmentExpression':
case 'BinaryExpression':
case 'LogicalExpression': {
return is_expression_async(expression.left) || is_expression_async(expression.right);
}
case 'CallExpression':
case 'NewExpression': {
return (
(expression.callee.type !== 'Super' && is_expression_async(expression.callee)) ||
expression.arguments.some((element) => {
if (element.type === 'SpreadElement') {
return is_expression_async(element.argument);
} else {
return is_expression_async(element);
}
})
);
}
case 'ChainExpression': {
return is_expression_async(expression.expression);
}
case 'ConditionalExpression': {
return (
is_expression_async(expression.test) ||
is_expression_async(expression.alternate) ||
is_expression_async(expression.consequent)
);
}
case 'ImportExpression': {
return is_expression_async(expression.source);
}
case 'MemberExpression': {
return (
(expression.object.type !== 'Super' && is_expression_async(expression.object)) ||
(expression.property.type !== 'PrivateIdentifier' &&
is_expression_async(expression.property))
);
}
case 'ObjectPattern':
case 'ObjectExpression': {
return expression.properties.some((property) => {
if (property.type === 'SpreadElement') {
return is_expression_async(property.argument);
} else if (property.type === 'Property') {
return (
(property.key.type !== 'PrivateIdentifier' && is_expression_async(property.key)) ||
is_expression_async(property.value)
);
}
});
}
case 'RestElement': {
return is_expression_async(expression.argument);
}
case 'SequenceExpression':
case 'TemplateLiteral': {
return expression.expressions.some((subexpression) => is_expression_async(subexpression));
}
case 'TaggedTemplateExpression': {
return is_expression_async(expression.tag) || is_expression_async(expression.quasi);
}
case 'UnaryExpression':
case 'UpdateExpression': {
return is_expression_async(expression.argument);
}
case 'YieldExpression': {
return expression.argument ? is_expression_async(expression.argument) : false;
}
default:
return false;
}
}
/**
* @template {import('./types').ClientTransformState} State
* @param {import('estree').AssignmentExpression} node
@ -153,17 +250,28 @@ export function serialize_set_binding(node, context, fallback) {
return fallback();
}
return b.call(
b.thunk(
b.block([
b.const(tmp_id, /** @type {import('estree').Expression} */ (visit(node.right))),
b.stmt(b.sequence(assignments)),
// return because it could be used in a nested expression where the value is needed.
// example: { foo: ({ bar } = { bar: 1 })}
b.return(b.id(tmp_id))
])
)
const rhs_expression = /** @type {import('estree').Expression} */ (visit(node.right));
const iife_is_async =
is_expression_async(rhs_expression) ||
assignments.some((assignment) => is_expression_async(assignment));
const iife = b.arrow(
[],
b.block([
b.const(tmp_id, rhs_expression),
b.stmt(b.sequence(assignments)),
// return because it could be used in a nested expression where the value is needed.
// example: { foo: ({ bar } = { bar: 1 })}
b.return(b.id(tmp_id))
])
);
if (iife_is_async) {
return b.await(b.call(b.async(iife)));
} else {
return b.call(iife);
}
}
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {

@ -44,6 +44,23 @@ export function assignment(operator, left, right) {
return { type: 'AssignmentExpression', operator, left, right };
}
/**
* @template T
* @param {T & import('estree').BaseFunction} func
* @returns {T & import('estree').BaseFunction}
*/
export function async(func) {
return { ...func, async: true };
}
/**
* @param {import('estree').Expression} argument
* @returns {import('estree').AwaitExpression}
*/
export function await_builder(argument) {
return { type: 'AwaitExpression', argument };
}
/**
* @param {import('estree').BinaryOperator} operator
* @param {import('estree').Expression} left
@ -573,6 +590,7 @@ export function throw_error(str) {
}
export {
await_builder as await,
new_builder as new,
let_builder as let,
const_builder as const,

@ -0,0 +1,75 @@
import { test } from '../../test';
export default test({
html: `
<button>Update me!</button>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
<p>0</p>
`,
async test({ assert, target, window }) {
const btn = target.querySelector('button');
const clickEvent = new window.Event('click', { bubbles: true });
await btn?.dispatchEvent(clickEvent);
for (let i = 1; i <= 42; i += 1) {
await Promise.resolve();
}
assert.htmlEqual(
target.innerHTML,
`
<button>Update me!</button>
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>
<p>11</p>
<p>12</p>
<p>13</p>
<p>14</p>
<p>15</p>
<p>16</p>
<p>17</p>
<p>18</p>
<p>19</p>
<p>20</p>
<p>21</p>
<p>22</p>
<p>23</p>
<p>24</p>
<p>25</p>
<p>26</p>
`
);
}
});

@ -0,0 +1,89 @@
<script>
let a = $state(0);
let b = $state(0);
let c = $state(0);
let d = $state(0);
let e = $state(0);
let f = $state(0);
let g = $state(0);
let h = $state(0);
let i = $state(0);
let j = $state(0);
let k = $state(0);
let l = $state(0);
let m = $state(0);
let n = $state(0);
let o = $state(0);
let p = $state(0);
let q = $state(0);
let r = $state(0);
let s = $state(0);
let t = $state(0);
let u = $state(0);
let v = $state(0);
let w = $state(0);
let x = $state(0);
let y = $state(0);
let z = $state(0);
const get_vwx = () => {
return Promise.resolve({ v: 22, rest: [23, 24] });
}
const get_y = () => {
return Promise.resolve([24, 25]);
}
const update = async () => {
[a, b] = [1, await Promise.resolve(2)];
({ c = await Promise.resolve(3), d } = { d: 4 });
[e] = [await Promise.resolve(2) + await Promise.resolve(3)];
({ f = false || await Promise.resolve(6) } = {});
let func = Promise.resolve(() => 7);
[g = (await func)()] = [];
let mult = (a, b) => (a * b);
({ h } = { h: mult(2, await Promise.resolve(4))});
[i] = [new Date(await Promise.resolve(9)).getTime()];
[j = "19" ? 10 : await Promise.resolve(11)] = [];
let obj = ({ [await Promise.resolve("prop")]: k } = { prop: 11 });
[l = obj[await Promise.resolve("prop")] + 1] = [];
[m] = [`${1}${await Promise.resolve("3")}`];
[n] = [-(await Promise.resolve(-14))];
[o] = [(console.log(15), await Promise.resolve(15))];
({ anotherprop: p = await Promise.resolve(16) } = obj);
let val1, val2;
({ val1 = (async function (x) { return await x; })(Promise.resolve(18)), r = await val1 }
= ({ val2 = (async (x) => await x)(Promise.resolve(17)), q = await val2 } = []));
({ u = 21 } = ({ t = await Promise.resolve(20) } = ([s] = [await Promise.resolve(19)])));
({ v, rest: [w] } = await get_vwx());
[x, y, ...{ z = 26 }] = await get_y();
}
</script>
<button on:click={update}>Update me!</button>
<p>{a}</p>
<p>{b}</p>
<p>{c}</p>
<p>{d}</p>
<p>{e}</p>
<p>{f}</p>
<p>{g}</p>
<p>{h}</p>
<p>{i}</p>
<p>{j}</p>
<p>{k}</p>
<p>{l}</p>
<p>{m}</p>
<p>{n}</p>
<p>{o}</p>
<p>{p}</p>
<p>{q}</p>
<p>{r}</p>
<p>{s}</p>
<p>{t}</p>
<p>{u}</p>
<p>{v}</p>
<p>{w}</p>
<p>{x}</p>
<p>{y}</p>
<p>{z}</p>
Loading…
Cancel
Save