diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js index 5f390e0012..c2635e73f1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js @@ -1,10 +1,12 @@ /** @import { AssignmentExpression, CallExpression, ClassBody, PropertyDefinition, Expression, PrivateIdentifier, MethodDefinition } from 'estree' */ /** @import { StateField } from '#compiler' */ /** @import { Context } from '../types' */ +import * as b from '#compiler/builders'; import { get_rune } from '../../scope.js'; import * as e from '../../../errors.js'; import { is_state_creation_rune } from '../../../../utils.js'; import { get_name } from '../../nodes.js'; +import { regex_invalid_identifier_chars } from '../../patterns.js'; /** * @param {ClassBody} node @@ -16,6 +18,18 @@ export function ClassBody(node, context) { return; } + /** @type {string[]} */ + const private_ids = []; + + for (const prop of node.body) { + if ( + (prop.type === 'MethodDefinition' || prop.type === 'PropertyDefinition') && + prop.key.type === 'PrivateIdentifier' + ) { + private_ids.push(prop.key.name); + } + } + /** @type {Record} */ const state_fields = {}; @@ -46,6 +60,8 @@ export function ClassBody(node, context) { state_fields[name] = { node, type: rune, + // @ts-expect-error for public state this is filled out in a moment + key: key.type === 'PrivateIdentifier' ? key : null, value: /** @type {CallExpression} */ (value) }; } @@ -84,6 +100,22 @@ export function ClassBody(node, context) { } } + for (const name in state_fields) { + if (name[0] === '#') { + continue; + } + + const field = state_fields[name]; + + let deconflicted = name.replace(regex_invalid_identifier_chars, '_'); + while (private_ids.includes(deconflicted)) { + deconflicted = '_' + deconflicted; + } + + private_ids.push(deconflicted); + field.key = b.private_id(deconflicted); + } + context.next({ ...context.state, state_fields diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index fe789852ef..711c1e64d8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -164,7 +164,6 @@ export function client_component(analysis, options) { events: new Set(), preserve_whitespace: options.preserveWhitespace, state_fields: {}, - backing_fields: {}, transform: {}, in_constructor: false, instance_level_snippets: [], @@ -672,7 +671,6 @@ export function client_module(analysis, options) { scope: analysis.module.scope, scopes: analysis.module.scopes, state_fields: {}, - backing_fields: {}, transform: {}, in_constructor: false }; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js index 294faa43dd..6885be810f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js @@ -68,12 +68,9 @@ function build_assignment(operator, left, right, context) { in_constructor: rune !== '$derived' && rune !== '$derived.by' }; - const l = b.member( - b.this, - left.property.type === 'PrivateIdentifier' - ? left.property - : context.state.backing_fields[name] - ); + const field = context.state.state_fields[name]; + + const l = b.member(b.this, field.key); const r = /** @type {Expression} */ (context.visit(right, child_state)); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js index 2867c456c5..56eae3a763 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js @@ -17,42 +17,10 @@ export function ClassBody(node, context) { return; } - /** @type {string[]} */ - const private_ids = []; - - for (const prop of node.body) { - if ( - (prop.type === 'MethodDefinition' || prop.type === 'PropertyDefinition') && - prop.key.type === 'PrivateIdentifier' - ) { - private_ids.push(prop.key.name); - } - } - - /** - * each `foo = $state()` needs a backing `#foo` field - * @type {Record} - */ - const backing_fields = {}; - - for (const name in state_fields) { - if (name[0] === '#') { - continue; - } - - let deconflicted = name.replace(regex_invalid_identifier_chars, '_'); - while (private_ids.includes(deconflicted)) { - deconflicted = '_' + deconflicted; - } - - private_ids.push(deconflicted); - backing_fields[name] = b.private_id(deconflicted); - } - /** @type {Array} */ const body = []; - const child_state = { ...context.state, state_fields, backing_fields }; + const child_state = { ...context.state, state_fields }; for (const name in state_fields) { if (name[0] === '#') { @@ -63,15 +31,14 @@ export function ClassBody(node, context) { // insert backing fields for stuff declared in the constructor if (field.node.type === 'AssignmentExpression') { - const backing = backing_fields[name]; - const member = b.member(b.this, backing); + const member = b.member(b.this, field.key); const should_proxy = field.type === '$state' && true; // TODO const key = b.key(name); body.push( - b.prop_def(backing, null), + b.prop_def(field.key, null), b.method('get', key, [], [b.return(b.call('$.get', member))]), @@ -109,14 +76,13 @@ export function ClassBody(node, context) { continue; } - const backing = backing_fields[name]; - const member = b.member(b.this, backing); + const member = b.member(b.this, field.key); const should_proxy = field.type === '$state' && true; // TODO body.push( b.prop_def( - backing, + field.key, /** @type {CallExpression} */ ( context.visit(definition.value ?? field.value, child_state) ) diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index ef4e1577d3..2c677116dc 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -98,7 +98,6 @@ export function server_component(analysis, options) { namespace: options.namespace, preserve_whitespace: options.preserveWhitespace, state_fields: {}, - backing_fields: {}, skip_hydration_boundaries: false }; @@ -394,8 +393,7 @@ export function server_module(analysis, options) { // to be present for `javascript_visitors_legacy` and so is included in module // transform state as well as component transform state legacy_reactive_statements: new Map(), - state_fields: {}, - backing_fields: {} + state_fields: {} }; const module = /** @type {Program} */ ( diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AssignmentExpression.js index abb073a7d2..e80187d013 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/AssignmentExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AssignmentExpression.js @@ -35,10 +35,12 @@ function build_assignment(operator, left, right, context) { const rune = get_rune(right, context.state.scope); if (rune) { + const field = context.state.state_fields[name]; + const key = left.property.type === 'PrivateIdentifier' || rune === '$state' || rune === '$state.raw' ? left.property - : context.state.backing_fields[name]; + : field.key; const l = b.member(b.this, key, key.type === 'Literal'); diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js index 03e058ea0f..fb3cf89026 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/ClassBody.js @@ -1,7 +1,6 @@ -/** @import { CallExpression, ClassBody, MethodDefinition, PrivateIdentifier, PropertyDefinition, StaticBlock } from 'estree' */ +/** @import { CallExpression, ClassBody, MethodDefinition, PropertyDefinition, StaticBlock } from 'estree' */ /** @import { Context } from '../types.js' */ import * as b from '#compiler/builders'; -import { regex_invalid_identifier_chars } from '../../../patterns.js'; import { get_name } from '../../../nodes.js'; /** @@ -17,42 +16,10 @@ export function ClassBody(node, context) { return; } - /** @type {string[]} */ - const private_ids = []; - - for (const prop of node.body) { - if ( - (prop.type === 'MethodDefinition' || prop.type === 'PropertyDefinition') && - prop.key.type === 'PrivateIdentifier' - ) { - private_ids.push(prop.key.name); - } - } - - /** - * each `foo = $state()` needs a backing `#foo` field - * @type {Record} - */ - const backing_fields = {}; - - for (const name in state_fields) { - if (name[0] === '#') { - continue; - } - - let deconflicted = name.replace(regex_invalid_identifier_chars, '_'); - while (private_ids.includes(deconflicted)) { - deconflicted = '_' + deconflicted; - } - - private_ids.push(deconflicted); - backing_fields[name] = b.private_id(deconflicted); - } - /** @type {Array} */ const body = []; - const child_state = { ...context.state, state_fields, backing_fields }; + const child_state = { ...context.state, state_fields }; for (const name in state_fields) { if (name[0] === '#') { @@ -66,11 +33,10 @@ export function ClassBody(node, context) { field.node.type === 'AssignmentExpression' && (field.type === '$derived' || field.type === '$derived.by') ) { - const backing = backing_fields[name]; - const member = b.member(b.this, backing); + const member = b.member(b.this, field.key); body.push( - b.prop_def(backing, null), + b.prop_def(field.key, null), b.method('get', b.key(name), [], [b.return(b.call(member))]) ); } @@ -100,12 +66,11 @@ export function ClassBody(node, context) { continue; } - const backing = backing_fields[name]; - const member = b.member(b.this, backing); + const member = b.member(b.this, field.key); body.push( b.prop_def( - backing, + field.key, /** @type {CallExpression} */ ( context.visit(definition.value ?? field.value, child_state) ) diff --git a/packages/svelte/src/compiler/phases/3-transform/types.d.ts b/packages/svelte/src/compiler/phases/3-transform/types.d.ts index cb866abef1..21da208876 100644 --- a/packages/svelte/src/compiler/phases/3-transform/types.d.ts +++ b/packages/svelte/src/compiler/phases/3-transform/types.d.ts @@ -10,5 +10,4 @@ export interface TransformState { readonly scopes: Map; readonly state_fields: Record; - readonly backing_fields: Record; } diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index f91bceac80..ef52552a2e 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -3,7 +3,12 @@ import type { Binding } from '../phases/scope.js'; import type { AST, Namespace } from './template.js'; import type { ICompileDiagnostic } from '../utils/compile_diagnostic.js'; import type { StateCreationRuneName } from '../../utils.js'; -import type { AssignmentExpression, CallExpression, PropertyDefinition } from 'estree'; +import type { + AssignmentExpression, + CallExpression, + PrivateIdentifier, + PropertyDefinition +} from 'estree'; /** The return value of `compile` from `svelte/compiler` */ export interface CompileResult { @@ -274,6 +279,7 @@ export interface ExpressionMetadata { export interface StateField { type: StateCreationRuneName; node: PropertyDefinition | AssignmentExpression; + key: PrivateIdentifier; value: CallExpression; }