diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 9acc9fb99e..7b484f1adb 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -1,7 +1,7 @@ /** @import * as ESTree from 'estree' */ /** @import { Binding, AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */ /** @import { AnalysisState, Visitors } from './types' */ -/** @import { Analysis, AwaitedStatement, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */ +/** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */ import { walk } from 'zimmerframe'; import { parse } from '../1-parse/acorn.js'; import * as e from '../../errors.js'; @@ -549,7 +549,12 @@ export function analyze_component(root, source, options) { snippets: new Set(), async_deriveds: new Set(), pickled_awaits: new Set(), - awaited_statements: new Map() + instance_body: { + sync: [], + async: [], + declarations: [], + hoisted: [] + } }; if (!runes) { @@ -693,177 +698,191 @@ export function analyze_component(root, source, options) { e.legacy_rest_props_invalid(rest_props_refs[0].node); } - if (instance.has_await) { - /** - * @param {ESTree.Node} expression - * @param {Scope} scope - * @param {Set} touched - * @param {Set} seen - */ - const touch = (expression, scope, touched, seen = new Set()) => { - if (seen.has(expression)) return; - seen.add(expression); - - walk( - expression, - { scope }, - { - ImportDeclaration(node) {}, - Identifier(node, context) { - const parent = /** @type {ESTree.Node} */ (context.path.at(-1)); - if (is_reference(node, parent)) { - const binding = context.state.scope.get(node.name); - if (binding) { - touched.add(binding); - - for (const assignment of binding.assignments) { - touch(assignment.value, assignment.scope, touched, seen); - } + /** + * @param {ESTree.Node} expression + * @param {Scope} scope + * @param {Set} touched + * @param {Set} seen + */ + const touch = (expression, scope, touched, seen = new Set()) => { + if (seen.has(expression)) return; + seen.add(expression); + + walk( + expression, + { scope }, + { + ImportDeclaration(node) {}, + Identifier(node, context) { + const parent = /** @type {ESTree.Node} */ (context.path.at(-1)); + if (is_reference(node, parent)) { + const binding = context.state.scope.get(node.name); + if (binding) { + touched.add(binding); + + for (const assignment of binding.assignments) { + touch(assignment.value, assignment.scope, touched, seen); } } } } - ); - }; + } + ); + }; + + /** + * @param {ESTree.Node} node + * @param {Set} seen + * @param {Set} reads + * @param {Set} writes + */ + const trace_references = (node, reads, writes, seen = new Set()) => { + if (seen.has(node)) return; + seen.add(node); /** - * @param {ESTree.Node} node - * @param {Set} seen - * @param {Set} reads - * @param {Set} writes + * @param {ESTree.Pattern} node + * @param {Scope} scope */ - const trace_references = (node, reads, writes, seen = new Set()) => { - if (seen.has(node)) return; - seen.add(node); - - /** - * @param {ESTree.Pattern} node - * @param {Scope} scope - */ - function update(node, scope) { - for (const pattern of unwrap_pattern(node)) { - const node = object(pattern); - if (!node) return; - - const binding = scope.get(node.name); - if (!binding) return; - - writes.add(binding); - } + function update(node, scope) { + for (const pattern of unwrap_pattern(node)) { + const node = object(pattern); + if (!node) return; + + const binding = scope.get(node.name); + if (!binding) return; + + writes.add(binding); } + } - walk( - node, - { scope: instance.scope }, - { - _(node, context) { - const scope = scopes.get(node); - if (scope) { - context.next({ scope }); - } else { - context.next(); - } - }, - AssignmentExpression(node, context) { - update(node.left, context.state.scope); - }, - UpdateExpression(node, context) { - update( - /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node.argument), - context.state.scope - ); - }, - CallExpression(node, context) { - // for now, assume everything touched by the callee ends up mutating the object - // TODO optimise this better - - // special case — no need to peek inside effects - const rune = get_rune(node, context.state.scope); - if (rune === '$effect') return; - - /** @type {Set} */ - const touched = new Set(); - touch(node, context.state.scope, touched); - - for (const b of touched) { - writes.add(b); - } - }, - // don't look inside functions until they are called - ArrowFunctionExpression(_, context) {}, - FunctionDeclaration(_, context) {}, - FunctionExpression(_, context) {}, - Identifier(node, context) { - const parent = /** @type {ESTree.Node} */ (context.path.at(-1)); - if (is_reference(node, parent)) { - const binding = context.state.scope.get(node.name); - if (binding) { - reads.add(binding); - } + walk( + node, + { scope: instance.scope }, + { + _(node, context) { + const scope = scopes.get(node); + if (scope) { + context.next({ scope }); + } else { + context.next(); + } + }, + AssignmentExpression(node, context) { + update(node.left, context.state.scope); + }, + UpdateExpression(node, context) { + update( + /** @type {ESTree.Identifier | ESTree.MemberExpression} */ (node.argument), + context.state.scope + ); + }, + CallExpression(node, context) { + // for now, assume everything touched by the callee ends up mutating the object + // TODO optimise this better + + // special case — no need to peek inside effects + const rune = get_rune(node, context.state.scope); + if (rune === '$effect') return; + + /** @type {Set} */ + const touched = new Set(); + touch(node, context.state.scope, touched); + + for (const b of touched) { + writes.add(b); + } + }, + // don't look inside functions until they are called + ArrowFunctionExpression(_, context) {}, + FunctionDeclaration(_, context) {}, + FunctionExpression(_, context) {}, + Identifier(node, context) { + const parent = /** @type {ESTree.Node} */ (context.path.at(-1)); + if (is_reference(node, parent)) { + const binding = context.state.scope.get(node.name); + if (binding) { + reads.add(binding); } } } - ); - }; + } + ); + }; - /** - * @param {ESTree.Statement | ESTree.VariableDeclarator | ESTree.FunctionDeclaration | ESTree.ClassDeclaration} node - */ - const push = (node) => { + let awaited = false; + + // TODO this should probably be attached to the scope? + var promises = b.id('$$promises'); + + /** + * @param {ESTree.Identifier} id + * @param {ESTree.Expression} blocker + */ + function push_declaration(id, blocker) { + analysis.instance_body.declarations.push(id); + + const binding = /** @type {Binding} */ (instance.scope.get(id.name)); + binding.blocker = blocker; + } + + for (let node of instance.ast.body) { + if (node.type === 'ImportDeclaration') { + analysis.instance_body.hoisted.push(node); + continue; + } + + if (node.type === 'ExportDefaultDeclaration' || node.type === 'ExportAllDeclaration') { + // these can't exist inside `