simplify/DRY

pull/15820/head
Rich Harris 4 months ago
parent 90e7e0dce5
commit a6cc07c425

@ -1,10 +1,12 @@
/** @import { AssignmentExpression, CallExpression, ClassBody, PropertyDefinition, Expression, PrivateIdentifier, MethodDefinition } from 'estree' */ /** @import { AssignmentExpression, CallExpression, ClassBody, PropertyDefinition, Expression, PrivateIdentifier, MethodDefinition } from 'estree' */
/** @import { StateField } from '#compiler' */ /** @import { StateField } from '#compiler' */
/** @import { Context } from '../types' */ /** @import { Context } from '../types' */
import * as b from '#compiler/builders';
import { get_rune } from '../../scope.js'; import { get_rune } from '../../scope.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import { is_state_creation_rune } from '../../../../utils.js'; import { is_state_creation_rune } from '../../../../utils.js';
import { get_name } from '../../nodes.js'; import { get_name } from '../../nodes.js';
import { regex_invalid_identifier_chars } from '../../patterns.js';
/** /**
* @param {ClassBody} node * @param {ClassBody} node
@ -16,6 +18,18 @@ export function ClassBody(node, context) {
return; 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<string, StateField>} */ /** @type {Record<string, StateField>} */
const state_fields = {}; const state_fields = {};
@ -46,6 +60,8 @@ export function ClassBody(node, context) {
state_fields[name] = { state_fields[name] = {
node, node,
type: rune, 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) 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.next({
...context.state, ...context.state,
state_fields state_fields

@ -164,7 +164,6 @@ export function client_component(analysis, options) {
events: new Set(), events: new Set(),
preserve_whitespace: options.preserveWhitespace, preserve_whitespace: options.preserveWhitespace,
state_fields: {}, state_fields: {},
backing_fields: {},
transform: {}, transform: {},
in_constructor: false, in_constructor: false,
instance_level_snippets: [], instance_level_snippets: [],
@ -672,7 +671,6 @@ export function client_module(analysis, options) {
scope: analysis.module.scope, scope: analysis.module.scope,
scopes: analysis.module.scopes, scopes: analysis.module.scopes,
state_fields: {}, state_fields: {},
backing_fields: {},
transform: {}, transform: {},
in_constructor: false in_constructor: false
}; };

@ -68,12 +68,9 @@ function build_assignment(operator, left, right, context) {
in_constructor: rune !== '$derived' && rune !== '$derived.by' in_constructor: rune !== '$derived' && rune !== '$derived.by'
}; };
const l = b.member( const field = context.state.state_fields[name];
b.this,
left.property.type === 'PrivateIdentifier' const l = b.member(b.this, field.key);
? left.property
: context.state.backing_fields[name]
);
const r = /** @type {Expression} */ (context.visit(right, child_state)); const r = /** @type {Expression} */ (context.visit(right, child_state));

@ -17,42 +17,10 @@ export function ClassBody(node, context) {
return; 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<string, PrivateIdentifier>}
*/
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<MethodDefinition | PropertyDefinition | StaticBlock>} */ /** @type {Array<MethodDefinition | PropertyDefinition | StaticBlock>} */
const body = []; const body = [];
const child_state = { ...context.state, state_fields, backing_fields }; const child_state = { ...context.state, state_fields };
for (const name in state_fields) { for (const name in state_fields) {
if (name[0] === '#') { if (name[0] === '#') {
@ -63,15 +31,14 @@ export function ClassBody(node, context) {
// insert backing fields for stuff declared in the constructor // insert backing fields for stuff declared in the constructor
if (field.node.type === 'AssignmentExpression') { if (field.node.type === 'AssignmentExpression') {
const backing = backing_fields[name]; const member = b.member(b.this, field.key);
const member = b.member(b.this, backing);
const should_proxy = field.type === '$state' && true; // TODO const should_proxy = field.type === '$state' && true; // TODO
const key = b.key(name); const key = b.key(name);
body.push( body.push(
b.prop_def(backing, null), b.prop_def(field.key, null),
b.method('get', key, [], [b.return(b.call('$.get', member))]), b.method('get', key, [], [b.return(b.call('$.get', member))]),
@ -109,14 +76,13 @@ export function ClassBody(node, context) {
continue; continue;
} }
const backing = backing_fields[name]; const member = b.member(b.this, field.key);
const member = b.member(b.this, backing);
const should_proxy = field.type === '$state' && true; // TODO const should_proxy = field.type === '$state' && true; // TODO
body.push( body.push(
b.prop_def( b.prop_def(
backing, field.key,
/** @type {CallExpression} */ ( /** @type {CallExpression} */ (
context.visit(definition.value ?? field.value, child_state) context.visit(definition.value ?? field.value, child_state)
) )

@ -98,7 +98,6 @@ export function server_component(analysis, options) {
namespace: options.namespace, namespace: options.namespace,
preserve_whitespace: options.preserveWhitespace, preserve_whitespace: options.preserveWhitespace,
state_fields: {}, state_fields: {},
backing_fields: {},
skip_hydration_boundaries: false 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 // to be present for `javascript_visitors_legacy` and so is included in module
// transform state as well as component transform state // transform state as well as component transform state
legacy_reactive_statements: new Map(), legacy_reactive_statements: new Map(),
state_fields: {}, state_fields: {}
backing_fields: {}
}; };
const module = /** @type {Program} */ ( const module = /** @type {Program} */ (

@ -35,10 +35,12 @@ function build_assignment(operator, left, right, context) {
const rune = get_rune(right, context.state.scope); const rune = get_rune(right, context.state.scope);
if (rune) { if (rune) {
const field = context.state.state_fields[name];
const key = const key =
left.property.type === 'PrivateIdentifier' || rune === '$state' || rune === '$state.raw' left.property.type === 'PrivateIdentifier' || rune === '$state' || rune === '$state.raw'
? left.property ? left.property
: context.state.backing_fields[name]; : field.key;
const l = b.member(b.this, key, key.type === 'Literal'); const l = b.member(b.this, key, key.type === 'Literal');

@ -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 { Context } from '../types.js' */
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { regex_invalid_identifier_chars } from '../../../patterns.js';
import { get_name } from '../../../nodes.js'; import { get_name } from '../../../nodes.js';
/** /**
@ -17,42 +16,10 @@ export function ClassBody(node, context) {
return; 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<string, PrivateIdentifier>}
*/
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<MethodDefinition | PropertyDefinition | StaticBlock>} */ /** @type {Array<MethodDefinition | PropertyDefinition | StaticBlock>} */
const body = []; const body = [];
const child_state = { ...context.state, state_fields, backing_fields }; const child_state = { ...context.state, state_fields };
for (const name in state_fields) { for (const name in state_fields) {
if (name[0] === '#') { if (name[0] === '#') {
@ -66,11 +33,10 @@ export function ClassBody(node, context) {
field.node.type === 'AssignmentExpression' && field.node.type === 'AssignmentExpression' &&
(field.type === '$derived' || field.type === '$derived.by') (field.type === '$derived' || field.type === '$derived.by')
) { ) {
const backing = backing_fields[name]; const member = b.member(b.this, field.key);
const member = b.member(b.this, backing);
body.push( body.push(
b.prop_def(backing, null), b.prop_def(field.key, null),
b.method('get', b.key(name), [], [b.return(b.call(member))]) b.method('get', b.key(name), [], [b.return(b.call(member))])
); );
} }
@ -100,12 +66,11 @@ export function ClassBody(node, context) {
continue; continue;
} }
const backing = backing_fields[name]; const member = b.member(b.this, field.key);
const member = b.member(b.this, backing);
body.push( body.push(
b.prop_def( b.prop_def(
backing, field.key,
/** @type {CallExpression} */ ( /** @type {CallExpression} */ (
context.visit(definition.value ?? field.value, child_state) context.visit(definition.value ?? field.value, child_state)
) )

@ -10,5 +10,4 @@ export interface TransformState {
readonly scopes: Map<AST.SvelteNode, Scope>; readonly scopes: Map<AST.SvelteNode, Scope>;
readonly state_fields: Record<string, StateField>; readonly state_fields: Record<string, StateField>;
readonly backing_fields: Record<string, PrivateIdentifier>;
} }

@ -3,7 +3,12 @@ import type { Binding } from '../phases/scope.js';
import type { AST, Namespace } from './template.js'; import type { AST, Namespace } from './template.js';
import type { ICompileDiagnostic } from '../utils/compile_diagnostic.js'; import type { ICompileDiagnostic } from '../utils/compile_diagnostic.js';
import type { StateCreationRuneName } from '../../utils.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` */ /** The return value of `compile` from `svelte/compiler` */
export interface CompileResult { export interface CompileResult {
@ -274,6 +279,7 @@ export interface ExpressionMetadata {
export interface StateField { export interface StateField {
type: StateCreationRuneName; type: StateCreationRuneName;
node: PropertyDefinition | AssignmentExpression; node: PropertyDefinition | AssignmentExpression;
key: PrivateIdentifier;
value: CallExpression; value: CallExpression;
} }

Loading…
Cancel
Save