|
|
|
@ -546,82 +546,112 @@ const javascript_visitors = {
|
|
|
|
|
|
|
|
|
|
/** @type {import('./types').Visitors} */
|
|
|
|
|
const javascript_visitors_runes = {
|
|
|
|
|
ClassBody(node, { state, visit, next }) {
|
|
|
|
|
if (!state.analysis.runes) {
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
/** @type {import('estree').PropertyDefinition[]} */
|
|
|
|
|
const deriveds = [];
|
|
|
|
|
/** @type {import('estree').MethodDefinition | null} */
|
|
|
|
|
let constructor = null;
|
|
|
|
|
// Get the constructor
|
|
|
|
|
ClassBody(node, { state, visit }) {
|
|
|
|
|
/** @type {Map<string, import('../../3-transform/client/types.js').StateField>} */
|
|
|
|
|
const public_derived = new Map();
|
|
|
|
|
|
|
|
|
|
/** @type {Map<string, import('../../3-transform/client/types.js').StateField>} */
|
|
|
|
|
const private_derived = new Map();
|
|
|
|
|
|
|
|
|
|
/** @type {string[]} */
|
|
|
|
|
const private_ids = [];
|
|
|
|
|
|
|
|
|
|
for (const definition of node.body) {
|
|
|
|
|
if (definition.type === 'MethodDefinition' && definition.kind === 'constructor') {
|
|
|
|
|
constructor = /** @type {import('estree').MethodDefinition} */ (visit(definition));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Move $derived() runes to the end of the body if there is a constructor
|
|
|
|
|
if (constructor !== null) {
|
|
|
|
|
const body = [];
|
|
|
|
|
for (const definition of node.body) {
|
|
|
|
|
if (
|
|
|
|
|
definition.type === 'PropertyDefinition' &&
|
|
|
|
|
(definition.key.type === 'Identifier' || definition.key.type === 'PrivateIdentifier')
|
|
|
|
|
) {
|
|
|
|
|
const is_private = definition.key.type === 'PrivateIdentifier';
|
|
|
|
|
|
|
|
|
|
if (definition.value?.type === 'CallExpression') {
|
|
|
|
|
const rune = get_rune(definition.value, state.scope);
|
|
|
|
|
|
|
|
|
|
if (rune === '$derived') {
|
|
|
|
|
deriveds.push(/** @type {import('estree').PropertyDefinition} */ (visit(definition)));
|
|
|
|
|
if (is_private) {
|
|
|
|
|
// Keep the private #name initializer if private, but remove initial value
|
|
|
|
|
body.push({
|
|
|
|
|
...definition,
|
|
|
|
|
value: null
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
if (
|
|
|
|
|
definition.type === 'PropertyDefinition' &&
|
|
|
|
|
(definition.key.type === 'Identifier' || definition.key.type === 'PrivateIdentifier')
|
|
|
|
|
) {
|
|
|
|
|
const { type, name } = definition.key;
|
|
|
|
|
|
|
|
|
|
const is_private = type === 'PrivateIdentifier';
|
|
|
|
|
if (is_private) private_ids.push(name);
|
|
|
|
|
|
|
|
|
|
if (definition.value?.type === 'CallExpression') {
|
|
|
|
|
const rune = get_rune(definition.value, state.scope);
|
|
|
|
|
if (rune === '$derived' || rune === '$derived.by') {
|
|
|
|
|
/** @type {import('../../3-transform/client/types.js').StateField} */
|
|
|
|
|
const field = {
|
|
|
|
|
kind: rune === '$derived.by' ? 'derived_call' : 'derived',
|
|
|
|
|
// @ts-expect-error this is set in the next pass
|
|
|
|
|
id: is_private ? definition.key : null
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (is_private) {
|
|
|
|
|
private_derived.set(name, field);
|
|
|
|
|
} else {
|
|
|
|
|
public_derived.set(name, field);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (definition.type !== 'MethodDefinition' || definition.kind !== 'constructor') {
|
|
|
|
|
body.push(
|
|
|
|
|
/** @type {import('estree').PropertyDefinition | import('estree').MethodDefinition | import('estree').StaticBlock} */ (
|
|
|
|
|
visit(definition)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (deriveds.length > 0) {
|
|
|
|
|
body.push({
|
|
|
|
|
...constructor,
|
|
|
|
|
value: {
|
|
|
|
|
...constructor.value,
|
|
|
|
|
body: b.block([
|
|
|
|
|
...constructor.value.body.body,
|
|
|
|
|
...deriveds.map((d) => {
|
|
|
|
|
return b.stmt(
|
|
|
|
|
b.assignment(
|
|
|
|
|
'=',
|
|
|
|
|
b.member(b.this, d.key),
|
|
|
|
|
/** @type {import('estree').Expression} */ (d.value)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// each `foo = $derived()` needs a backing `#foo` field
|
|
|
|
|
for (const [name, field] of public_derived) {
|
|
|
|
|
let deconflicted = name;
|
|
|
|
|
while (private_ids.includes(deconflicted)) {
|
|
|
|
|
deconflicted = '_' + deconflicted;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private_ids.push(deconflicted);
|
|
|
|
|
field.id = b.private_id(deconflicted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @type {Array<import('estree').MethodDefinition | import('estree').PropertyDefinition>} */
|
|
|
|
|
const body = [];
|
|
|
|
|
|
|
|
|
|
const child_state = { ...state, private_derived };
|
|
|
|
|
|
|
|
|
|
// Replace parts of the class body
|
|
|
|
|
for (const definition of node.body) {
|
|
|
|
|
if (
|
|
|
|
|
definition.type === 'PropertyDefinition' &&
|
|
|
|
|
(definition.key.type === 'Identifier' || definition.key.type === 'PrivateIdentifier')
|
|
|
|
|
) {
|
|
|
|
|
const name = definition.key.name;
|
|
|
|
|
|
|
|
|
|
const is_private = definition.key.type === 'PrivateIdentifier';
|
|
|
|
|
const field = (is_private ? private_derived : public_derived).get(name);
|
|
|
|
|
|
|
|
|
|
if (definition.value?.type === 'CallExpression' && field !== undefined) {
|
|
|
|
|
const init = /** @type {import('estree').Expression} **/ (
|
|
|
|
|
visit(definition.value.arguments[0], child_state)
|
|
|
|
|
);
|
|
|
|
|
const value =
|
|
|
|
|
field.kind === 'derived_call'
|
|
|
|
|
? b.call('$.once', init)
|
|
|
|
|
: b.call('$.once', b.thunk(init));
|
|
|
|
|
|
|
|
|
|
if (is_private) {
|
|
|
|
|
body.push(b.prop_def(field.id, value));
|
|
|
|
|
} else {
|
|
|
|
|
// #foo;
|
|
|
|
|
const member = b.member(b.this, field.id);
|
|
|
|
|
body.push(b.prop_def(field.id, value));
|
|
|
|
|
|
|
|
|
|
// get foo() { return this.#foo; }
|
|
|
|
|
body.push(b.method('get', definition.key, [], [b.return(b.call(member))]));
|
|
|
|
|
|
|
|
|
|
if ((field.kind === 'derived' || field.kind === 'derived_call') && state.options.dev) {
|
|
|
|
|
body.push(
|
|
|
|
|
b.method(
|
|
|
|
|
'set',
|
|
|
|
|
definition.key,
|
|
|
|
|
[b.id('_')],
|
|
|
|
|
[b.throw_error(`Cannot update a derived property ('${name}')`)]
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
body.push(constructor);
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
...node,
|
|
|
|
|
body
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
body.push(/** @type {import('estree').MethodDefinition} **/ (visit(definition, child_state)));
|
|
|
|
|
}
|
|
|
|
|
next();
|
|
|
|
|
|
|
|
|
|
return { ...node, body };
|
|
|
|
|
},
|
|
|
|
|
PropertyDefinition(node, { state, next, visit }) {
|
|
|
|
|
if (node.value != null && node.value.type === 'CallExpression') {
|
|
|
|
@ -730,6 +760,16 @@ const javascript_visitors_runes = {
|
|
|
|
|
return transform_inspect_rune(node, context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.next();
|
|
|
|
|
},
|
|
|
|
|
MemberExpression(node, context) {
|
|
|
|
|
if (node.object.type === 'ThisExpression' && node.property.type === 'PrivateIdentifier') {
|
|
|
|
|
const field = context.state.private_derived.get(node.property.name);
|
|
|
|
|
if (field) {
|
|
|
|
|
return b.call(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.next();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
@ -2089,7 +2129,8 @@ export function server_component(analysis, options) {
|
|
|
|
|
metadata: {
|
|
|
|
|
namespace: options.namespace
|
|
|
|
|
},
|
|
|
|
|
preserve_whitespace: options.preserveWhitespace
|
|
|
|
|
preserve_whitespace: options.preserveWhitespace,
|
|
|
|
|
private_derived: new Map()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const module = /** @type {import('estree').Program} */ (
|
|
|
|
@ -2346,7 +2387,8 @@ export function server_module(analysis, options) {
|
|
|
|
|
// this is an anomaly — it can only be used in components, but it needs
|
|
|
|
|
// to be present for `javascript_visitors` and so is included in module
|
|
|
|
|
// transform state as well as component transform state
|
|
|
|
|
legacy_reactive_statements: new Map()
|
|
|
|
|
legacy_reactive_statements: new Map(),
|
|
|
|
|
private_derived: new Map()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const module = /** @type {import('estree').Program} */ (
|
|
|
|
|