svelte
Version:
Cybernetically enhanced web apps
137 lines (112 loc) • 3.78 kB
JavaScript
/** @import { CallExpression, Expression } from 'estree' */
/** @import { Context } from '../types' */
import { dev, is_ignored } from '../../../../state.js';
import * as b from '#compiler/builders';
import { get_rune } from '../../../scope.js';
import { should_proxy } from '../utils.js';
import { get_inspect_args } from '../../utils.js';
/**
* @param {CallExpression} node
* @param {Context} context
*/
export function CallExpression(node, context) {
const rune = get_rune(node, context.state.scope);
switch (rune) {
case '$host':
return b.id('$$props.$$host');
case '$effect.tracking':
return b.call('$.effect_tracking');
// transform state field assignments in constructors
case '$state':
case '$state.raw': {
let arg = node.arguments[0];
/** @type {Expression | undefined} */
let value = undefined;
if (arg) {
value = /** @type {Expression} */ (context.visit(node.arguments[0]));
if (
rune === '$state' &&
should_proxy(/** @type {Expression} */ (arg), context.state.scope)
) {
value = b.call('$.proxy', value);
}
}
const callee = b.id('$.state', node.callee.loc);
return b.call(callee, value);
}
case '$derived':
case '$derived.by': {
let fn = /** @type {Expression} */ (context.visit(node.arguments[0]));
return b.call('$.derived', rune === '$derived' ? b.thunk(fn) : fn);
}
case '$state.eager':
return b.call(
'$.eager',
b.thunk(/** @type {Expression} */ (context.visit(node.arguments[0])))
);
case '$state.snapshot':
return b.call(
'$.snapshot',
/** @type {Expression} */ (context.visit(node.arguments[0])),
is_ignored(node, 'state_snapshot_uncloneable') && b.true
);
case '$effect':
case '$effect.pre': {
const callee = rune === '$effect' ? '$.user_effect' : '$.user_pre_effect';
const func = /** @type {Expression} */ (context.visit(node.arguments[0]));
const expr = b.call(callee, /** @type {Expression} */ (func));
expr.callee.loc = node.callee.loc; // ensure correct mapping
return expr;
}
case '$effect.root':
return b.call(
'$.effect_root',
.../** @type {Expression[]} */ (node.arguments.map((arg) => context.visit(arg)))
);
case '$effect.pending':
return b.call('$.eager', b.thunk(b.call('$.pending')));
case '$inspect':
case '$inspect().with':
return transform_inspect_rune(rune, node, context);
}
if (
dev &&
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'console' &&
context.state.scope.get('console') === null &&
node.callee.property.type === 'Identifier' &&
['debug', 'dir', 'error', 'group', 'groupCollapsed', 'info', 'log', 'trace', 'warn'].includes(
node.callee.property.name
) &&
node.arguments.some(
(arg) => arg.type === 'SpreadElement' || context.state.scope.evaluate(arg).has_unknown
)
) {
return b.call(
node.callee,
b.spread(
b.call(
'$.log_if_contains_state',
b.literal(node.callee.property.name),
.../** @type {Expression[]} */ (node.arguments.map((arg) => context.visit(arg)))
)
)
);
}
context.next();
}
/**
* @param {'$inspect' | '$inspect().with'} rune
* @param {CallExpression} node
* @param {Context} context
*/
function transform_inspect_rune(rune, node, context) {
if (!dev) return b.empty;
const { args, inspector } = get_inspect_args(rune, node, context.visit);
// by passing an arrow function, the log appears to come from the `$inspect` callsite
// rather than the `inspect.js` file containing the utility
const id = b.id('$$args');
const fn = b.arrow([b.rest(id)], b.call(inspector, b.spread(id)));
return b.call('$.inspect', b.thunk(b.array(args)), fn, rune === '$inspect' && b.true);
}