Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 5x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 2x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 2x 3x 2x 2x 2x 2x 2x 2x 2x 3x 3x 3x 1x 1x 1x | /** @import { ClassBody, Expression, MethodDefinition, PropertyDefinition } from 'estree' */ /** @import { Context } from '../types.js' */ /** @import { StateField } from '../../client/types.js' */ import { dev } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; import { get_rune } from '../../../scope.js'; /** * @param {ClassBody} node * @param {Context} context */ export function ClassBody(node, context) { if (!context.state.analysis.runes) { context.next(); return; } /** @type {Map<string, StateField>} */ const public_derived = new Map(); /** @type {Map<string, StateField>} */ const private_derived = new Map(); /** @type {string[]} */ const private_ids = []; for (const definition of node.body) { 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, context.state.scope); if (rune === '$derived' || rune === '$derived.by') { /** @type {StateField} */ const field = { kind: rune === '$derived.by' ? 'derived_by' : '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); } } } } } // 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<MethodDefinition | PropertyDefinition>} */ const body = []; const child_state = { ...context.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 {Expression} **/ ( context.visit(definition.value.arguments[0], child_state) ); const value = field.kind === 'derived_by' ? 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 (dev && (field.kind === 'derived' || field.kind === 'derived_by')) { body.push( b.method( 'set', definition.key, [b.id('_')], [b.throw_error(`Cannot update a derived property ('${name}')`)] ) ); } } continue; } } body.push(/** @type {MethodDefinition} **/ (context.visit(definition, child_state))); } return { ...node, body }; } |