pull/15844/head
Rich Harris 3 months ago
parent 73fb7a1904
commit 80de2ca1ef

@ -77,10 +77,6 @@ import { UseDirective } from './visitors/UseDirective.js';
import { VariableDeclarator } from './visitors/VariableDeclarator.js';
import is_reference from 'is-reference';
import { mark_subtree_dynamic } from './visitors/shared/fragment.js';
import { is_last_evaluated_expression } from './utils/awaits.js';
/** @type {Array<ExpressionMetadata | null>} */
const metadata_stack = [];
/**
* @type {Visitors}
@ -132,23 +128,9 @@ const visitors = {
ignore_map.set(node, structuredClone(ignore_stack));
metadata_stack.push(state.expression);
const scope = state.scopes.get(node);
next(scope !== undefined && scope !== state.scope ? { ...state, scope } : state);
metadata_stack.pop();
// if this node set `state.expression`, now that we've visited it we can determine
// which `await` expressions need to be wrapped in `$.save(...)`
if (state.expression && metadata_stack[metadata_stack.length - 1] === null) {
for (const { path, node } of state.expression.awaits) {
if (!is_last_evaluated_expression(path, node)) {
state.analysis.context_preserving_awaits.add(node);
}
}
}
if (ignores.length > 0) {
pop_ignore();
}
@ -291,7 +273,6 @@ export function analyze_module(source, options) {
immutable: true,
tracing: false,
async_deriveds: new Set(),
context_preserving_awaits: new Set(),
comments,
classes: new Map()
};
@ -538,8 +519,7 @@ export function analyze_component(root, source, options) {
undefined_exports: new Map(),
snippet_renderers: new Map(),
snippets: new Set(),
async_deriveds: new Set(),
context_preserving_awaits: new Set()
async_deriveds: new Set()
};
if (!runes) {

@ -1,70 +0,0 @@
/** @import { Expression, Property, SpreadElement } from 'estree' */
/** @import { AST } from '#compiler' */
/**
*
* @param {AST.SvelteNode[]} path
* @param {Expression | SpreadElement | Property} node
*/
export function is_last_evaluated_expression(path, node) {
let i = path.length;
while (i--) {
const parent = /** @type {Expression | Property | SpreadElement} */ (path[i]);
// @ts-expect-error we could probably use a neater/more robust mechanism
if (parent.metadata) {
return true;
}
switch (parent.type) {
case 'ArrayExpression':
if (node !== parent.elements.at(-1)) return false;
break;
case 'AssignmentExpression':
case 'BinaryExpression':
case 'LogicalExpression':
if (node === parent.left) return false;
break;
case 'CallExpression':
case 'NewExpression':
if (node !== parent.arguments.at(-1)) return false;
break;
case 'ConditionalExpression':
if (node === parent.test) return false;
break;
case 'MemberExpression':
if (parent.computed && node === parent.object) return false;
break;
case 'ObjectExpression':
if (node !== parent.properties.at(-1)) return false;
break;
case 'Property':
if (node === parent.key) return false;
break;
case 'SequenceExpression':
if (node !== parent.expressions.at(-1)) return false;
break;
case 'TaggedTemplateExpression':
if (node !== parent.quasi.expressions.at(-1)) return false;
break;
case 'TemplateLiteral':
if (node !== parent.expressions.at(-1)) return false;
break;
default:
return false;
}
node = parent;
}
}

@ -7,20 +7,15 @@ import * as e from '../../../errors.js';
* @param {Context} context
*/
export function AwaitExpression(node, context) {
const tla = context.state.ast_type === 'instance' && context.state.function_depth === 1;
if (tla) {
context.state.analysis.context_preserving_awaits.add(node);
}
let suspend = tla;
let suspend = context.state.ast_type === 'instance' && context.state.function_depth === 1;
if (context.state.expression) {
context.state.expression.awaits.push({ node, path: context.path.slice() });
context.state.expression.has_await = true;
suspend = true;
}
// disallow top-level `await` or `await` in template expressions
// unless a) in runes mode and b) opted into `experimental.async`
if (suspend) {
if (!context.state.options.experimental.async) {
e.experimental_async(node);

@ -702,7 +702,8 @@ export function client_module(analysis, options) {
scopes: analysis.module.scopes,
state_fields: new Map(),
transform: {},
in_constructor: false
in_constructor: false,
is_instance: false
};
const module = /** @type {ESTree.Program} */ (

@ -21,6 +21,9 @@ export interface ClientTransformState extends TransformState {
*/
readonly in_constructor: boolean;
/** `true` if we're transforming the contents of `<script>` */
readonly is_instance: boolean;
readonly transform: Record<
string,
{
@ -41,7 +44,6 @@ export interface ComponentClientTransformState extends ClientTransformState {
readonly options: ValidatedCompileOptions;
readonly hoisted: Array<Statement | ModuleDeclaration>;
readonly events: Set<string>;
readonly is_instance: boolean;
readonly store_to_invalidate?: string;
/** Stuff that happens before the render effect(s) */

@ -1,4 +1,5 @@
/** @import { AwaitExpression, Expression } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { AwaitExpression, Expression, Property, SpreadElement } from 'estree' */
/** @import { Context } from '../types' */
import { dev } from '../../../../state.js';
import * as b from '../../../../utils/builders.js';
@ -8,7 +9,9 @@ import * as b from '../../../../utils/builders.js';
* @param {Context} context
*/
export function AwaitExpression(node, context) {
const save = context.state.analysis.context_preserving_awaits.has(node);
const tla = context.state.is_instance && context.state.scope.function_depth === 1;
const save = tla || !is_last_evaluated_expression(context.path, node);
if (dev || save) {
return b.call(
@ -24,3 +27,74 @@ export function AwaitExpression(node, context) {
return context.next();
}
/**
*
* @param {AST.SvelteNode[]} path
* @param {Expression | SpreadElement | Property} node
*/
export function is_last_evaluated_expression(path, node) {
let i = path.length;
while (i--) {
const parent = /** @type {Expression | Property | SpreadElement} */ (path[i]);
console.log(parent.start, parent.type);
// @ts-expect-error we could probably use a neater/more robust mechanism
if (parent.metadata) {
return true;
}
switch (parent.type) {
case 'ArrayExpression':
if (node !== parent.elements.at(-1)) return false;
break;
case 'AssignmentExpression':
case 'BinaryExpression':
case 'LogicalExpression':
if (node === parent.left) return false;
break;
case 'CallExpression':
case 'NewExpression':
if (node !== parent.arguments.at(-1)) return false;
break;
case 'ConditionalExpression':
if (node === parent.test) return false;
break;
case 'MemberExpression':
if (parent.computed && node === parent.object) return false;
break;
case 'ObjectExpression':
if (node !== parent.properties.at(-1)) return false;
break;
case 'Property':
if (node === parent.key) return false;
break;
case 'SequenceExpression':
if (node !== parent.expressions.at(-1)) return false;
break;
case 'TaggedTemplateExpression':
if (node !== parent.quasi.expressions.at(-1)) return false;
break;
case 'TemplateLiteral':
if (node !== parent.expressions.at(-1)) return false;
break;
default:
console.log('bailing', parent.start, parent.type);
return false;
}
node = parent;
}
}

@ -67,8 +67,7 @@ export function create_expression_metadata() {
has_call: false,
has_member_expression: false,
has_assignment: false,
has_await: false,
awaits: []
has_await: false
};
}

@ -47,9 +47,6 @@ export interface Analysis {
/** A set of deriveds that contain `await` expressions */
async_deriveds: Set<CallExpression>;
/** A set of `await` expressions that should preserve context */
context_preserving_awaits: Set<AwaitExpression>;
}
export interface ComponentAnalysis extends Analysis {

@ -299,8 +299,6 @@ export interface ExpressionMetadata {
has_member_expression: boolean;
/** True if the expression includes an assignment or an update */
has_assignment: boolean;
/** An array of await expressions, so we can figure out which ones need context preservation */
awaits: Array<{ node: AwaitExpression; path: AST.SvelteNode[] }>;
}
export interface StateField {

Loading…
Cancel
Save