|
|
|
@ -6,7 +6,12 @@ import { walk } from 'zimmerframe';
|
|
|
|
import { parse } from '../1-parse/acorn.js';
|
|
|
|
import { parse } from '../1-parse/acorn.js';
|
|
|
|
import * as e from '../../errors.js';
|
|
|
|
import * as e from '../../errors.js';
|
|
|
|
import * as w from '../../warnings.js';
|
|
|
|
import * as w from '../../warnings.js';
|
|
|
|
import { extract_identifiers, has_await_expression } from '../../utils/ast.js';
|
|
|
|
import {
|
|
|
|
|
|
|
|
extract_identifiers,
|
|
|
|
|
|
|
|
has_await_expression,
|
|
|
|
|
|
|
|
object,
|
|
|
|
|
|
|
|
unwrap_pattern
|
|
|
|
|
|
|
|
} from '../../utils/ast.js';
|
|
|
|
import * as b from '#compiler/builders';
|
|
|
|
import * as b from '#compiler/builders';
|
|
|
|
import { Scope, ScopeRoot, create_scopes, get_rune, set_scope } from '../scope.js';
|
|
|
|
import { Scope, ScopeRoot, create_scopes, get_rune, set_scope } from '../scope.js';
|
|
|
|
import check_graph_for_cycles from './utils/check_graph_for_cycles.js';
|
|
|
|
import check_graph_for_cycles from './utils/check_graph_for_cycles.js';
|
|
|
|
@ -543,7 +548,13 @@ export function analyze_component(root, source, options) {
|
|
|
|
snippet_renderers: new Map(),
|
|
|
|
snippet_renderers: new Map(),
|
|
|
|
snippets: new Set(),
|
|
|
|
snippets: new Set(),
|
|
|
|
async_deriveds: new Set(),
|
|
|
|
async_deriveds: new Set(),
|
|
|
|
pickled_awaits: new Set()
|
|
|
|
pickled_awaits: new Set(),
|
|
|
|
|
|
|
|
instance_body: {
|
|
|
|
|
|
|
|
sync: [],
|
|
|
|
|
|
|
|
async: [],
|
|
|
|
|
|
|
|
declarations: [],
|
|
|
|
|
|
|
|
hoisted: []
|
|
|
|
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (!runes) {
|
|
|
|
if (!runes) {
|
|
|
|
@ -676,6 +687,194 @@ export function analyze_component(root, source, options) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @param {ESTree.Node} expression
|
|
|
|
|
|
|
|
* @param {Scope} scope
|
|
|
|
|
|
|
|
* @param {Set<Binding>} touched
|
|
|
|
|
|
|
|
* @param {Set<ESTree.Node>} 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<ESTree.Node>} seen
|
|
|
|
|
|
|
|
* @param {Set<Binding>} reads
|
|
|
|
|
|
|
|
* @param {Set<Binding>} writes
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 as they only run once async work has completed
|
|
|
|
|
|
|
|
const rune = get_rune(node, context.state.scope);
|
|
|
|
|
|
|
|
if (rune === '$effect') return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Set<Binding>} */
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 `<script>` but TypeScript doesn't know that
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (node.type === 'ExportNamedDeclaration') {
|
|
|
|
|
|
|
|
if (node.declaration) {
|
|
|
|
|
|
|
|
node = node.declaration;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const has_await = has_await_expression(node);
|
|
|
|
|
|
|
|
awaited ||= has_await;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (awaited && node.type !== 'FunctionDeclaration') {
|
|
|
|
|
|
|
|
/** @type {Set<Binding>} */
|
|
|
|
|
|
|
|
const reads = new Set(); // TODO we're not actually using this yet
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {Set<Binding>} */
|
|
|
|
|
|
|
|
const writes = new Set();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trace_references(node, reads, writes);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const blocker = b.member(promises, b.literal(analysis.instance_body.async.length), true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const binding of writes) {
|
|
|
|
|
|
|
|
binding.blocker = blocker;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (node.type === 'VariableDeclaration') {
|
|
|
|
|
|
|
|
for (const declarator of node.declarations) {
|
|
|
|
|
|
|
|
for (const id of extract_identifiers(declarator.id)) {
|
|
|
|
|
|
|
|
push_declaration(id, blocker);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// one declarator per declaration, makes things simpler
|
|
|
|
|
|
|
|
analysis.instance_body.async.push({
|
|
|
|
|
|
|
|
node: declarator,
|
|
|
|
|
|
|
|
has_await
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (node.type === 'ClassDeclaration') {
|
|
|
|
|
|
|
|
push_declaration(node.id, blocker);
|
|
|
|
|
|
|
|
analysis.instance_body.async.push({ node, has_await });
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
analysis.instance_body.async.push({ node, has_await });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
analysis.instance_body.sync.push(node);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (analysis.runes) {
|
|
|
|
if (analysis.runes) {
|
|
|
|
const props_refs = module.scope.references.get('$$props');
|
|
|
|
const props_refs = module.scope.references.get('$$props');
|
|
|
|
if (props_refs) {
|
|
|
|
if (props_refs) {
|
|
|
|
|