pull/15820/head
Rich Harris 4 months ago
parent c407dc09de
commit 2efb766f23

@ -1,4 +1,4 @@
/** @import { AssignmentExpression, 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 { get_rune } from '../../scope.js'; import { get_rune } from '../../scope.js';
@ -48,7 +48,8 @@ export function ClassBody(node, context) {
state_fields[name] = { state_fields[name] = {
node, node,
type: rune type: rune,
value: /** @type {CallExpression} */ (value)
}; };
} }

@ -163,7 +163,8 @@ export function client_component(analysis, options) {
}, },
events: new Set(), events: new Set(),
preserve_whitespace: options.preserveWhitespace, preserve_whitespace: options.preserveWhitespace,
class_transformer: null, public_state: new Map(),
private_state: new Map(),
transform: {}, transform: {},
in_constructor: false, in_constructor: false,
instance_level_snippets: [], instance_level_snippets: [],
@ -670,9 +671,10 @@ export function client_module(analysis, options) {
options, options,
scope: analysis.module.scope, scope: analysis.module.scope,
scopes: analysis.module.scopes, scopes: analysis.module.scopes,
public_state: new Map(),
private_state: new Map(),
transform: {}, transform: {},
in_constructor: false, in_constructor: false
class_transformer: null
}; };
const module = /** @type {ESTree.Program} */ ( const module = /** @type {ESTree.Program} */ (

@ -3,6 +3,7 @@ import type {
Statement, Statement,
LabeledStatement, LabeledStatement,
Identifier, Identifier,
PrivateIdentifier,
Expression, Expression,
AssignmentExpression, AssignmentExpression,
UpdateExpression, UpdateExpression,
@ -12,10 +13,10 @@ import type { AST, Namespace, ValidatedCompileOptions } from '#compiler';
import type { TransformState } from '../types.js'; import type { TransformState } from '../types.js';
import type { ComponentAnalysis } from '../../types.js'; import type { ComponentAnalysis } from '../../types.js';
import type { SourceLocation } from '#shared'; import type { SourceLocation } from '#shared';
import type { ClassTransformer } from '../shared/types.js';
export interface ClientTransformState extends TransformState { export interface ClientTransformState extends TransformState {
readonly class_transformer: ClassTransformer<Context> | null; readonly private_state: Map<string, StateField>;
readonly public_state: Map<string, StateField>;
/** /**
* `true` if the current lexical scope belongs to a class constructor. this allows * `true` if the current lexical scope belongs to a class constructor. this allows
@ -93,6 +94,10 @@ export interface ComponentClientTransformState extends ClientTransformState {
readonly module_level_snippets: VariableDeclaration[]; readonly module_level_snippets: VariableDeclaration[];
} }
export interface StateField {
type: '$state' | '$state.raw' | '$derived' | '$derived.by';
}
export type Context = import('zimmerframe').Context<AST.SvelteNode, ClientTransformState>; export type Context = import('zimmerframe').Context<AST.SvelteNode, ClientTransformState>;
export type Visitors = import('zimmerframe').Visitors<AST.SvelteNode, any>; export type Visitors = import('zimmerframe').Visitors<AST.SvelteNode, any>;

@ -1,4 +1,4 @@
/** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Pattern, MemberExpression, ThisExpression, PrivateIdentifier, CallExpression } from 'estree' */ /** @import { AssignmentExpression, AssignmentOperator, Expression, Identifier, Pattern } from 'estree' */
/** @import { AST } from '#compiler' */ /** @import { AST } from '#compiler' */
/** @import { Context } from '../types.js' */ /** @import { Context } from '../types.js' */
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
@ -11,17 +11,14 @@ import { dev, locate_node } from '../../../../state.js';
import { should_proxy } from '../utils.js'; import { should_proxy } from '../utils.js';
import { visit_assignment_expression } from '../../shared/assignments.js'; import { visit_assignment_expression } from '../../shared/assignments.js';
import { validate_mutation } from './shared/utils.js'; import { validate_mutation } from './shared/utils.js';
import { get_rune } from '../../../scope.js';
import { get_name } from '../../../nodes.js';
/** /**
* @param {AssignmentExpression} node * @param {AssignmentExpression} node
* @param {Context} context * @param {Context} context
*/ */
export function AssignmentExpression(node, context) { export function AssignmentExpression(node, context) {
const stripped_node = context.state.class_transformer?.generate_assignment(node, context);
if (stripped_node) {
return stripped_node;
}
const expression = /** @type {Expression} */ ( const expression = /** @type {Expression} */ (
visit_assignment_expression(node, context, build_assignment) ?? context.next() visit_assignment_expression(node, context, build_assignment) ?? context.next()
); );
@ -55,25 +52,50 @@ const callees = {
* @returns {Expression | null} * @returns {Expression | null}
*/ */
function build_assignment(operator, left, right, context) { function build_assignment(operator, left, right, context) {
// Handle class private/public state assignment cases if (context.state.analysis.runes && left.type === 'MemberExpression') {
if ( // special case — state declaration in class constructor
context.state.analysis.runes && const ancestor = context.path.at(-4);
left.type === 'MemberExpression' &&
left.property.type === 'PrivateIdentifier' if (ancestor?.type === 'MethodDefinition' && ancestor.kind === 'constructor') {
) { const rune = get_rune(right, context.state.scope);
const private_state = context.state.class_transformer?.get_field(left.property.name, true);
if (rune) {
const name = get_name(left.property);
const child_state = {
...context.state,
in_constructor: rune !== '$derived' && rune !== '$derived.by'
};
if (private_state !== undefined) { const l = b.member(
let value = /** @type {Expression} */ ( b.this,
context.visit(build_assignment_value(operator, left, right)) left.property.type === 'PrivateIdentifier'
); ? left.property
: context.state.backing_fields[name]
);
const needs_proxy = const r = /** @type {Expression} */ (context.visit(right, child_state));
private_state?.kind === '$state' &&
is_non_coercive_operator(operator) &&
should_proxy(value, context.state.scope);
return b.call('$.set', left, value, needs_proxy && b.true); return b.assignment(operator, l, r);
}
}
// special case — assignment to private state field
if (left.property.type === 'PrivateIdentifier') {
const private_state = context.state.private_state.get(left.property.name);
if (private_state !== undefined) {
let value = /** @type {Expression} */ (
context.visit(build_assignment_value(operator, left, right))
);
const needs_proxy =
private_state.type === '$state' &&
is_non_coercive_operator(operator) &&
should_proxy(value, context.state.scope);
return b.call('$.set', left, value, needs_proxy && b.true);
}
} }
} }

@ -10,13 +10,34 @@ import { transform_inspect_rune } from '../../utils.js';
* @param {Context} context * @param {Context} context
*/ */
export function CallExpression(node, context) { export function CallExpression(node, context) {
switch (get_rune(node, context.state.scope)) { const rune = get_rune(node, context.state.scope);
switch (rune) {
case '$host': case '$host':
return b.id('$$props.$$host'); return b.id('$$props.$$host');
case '$effect.tracking': case '$effect.tracking':
return b.call('$.effect_tracking'); return b.call('$.effect_tracking');
case '$state':
case '$state.raw': {
let should_proxy = rune === '$state' && true; // TODO
return b.call(
'$.state',
node.arguments[0] && /** @type {Expression} */ (context.visit(node.arguments[0])),
should_proxy && b.true
);
}
case '$derived':
case '$derived.by': {
let fn = /** @type {Expression} */ (context.visit(node.arguments[0]));
if (rune === '$derived') fn = b.thunk(fn);
return b.call('$.derived', fn);
}
case '$state.snapshot': case '$state.snapshot':
return b.call( return b.call(
'$.snapshot', '$.snapshot',

@ -1,19 +1,218 @@
/** @import { ClassBody } from 'estree' */ /** @import { CallExpression, ClassBody, Expression, Identifier, Literal, MethodDefinition, PrivateIdentifier, PropertyDefinition, StaticBlock } from 'estree' */
/** @import { Context } from '../types' */ /** @import { Context, StateField } from '../types' */
import { create_client_class_transformer } from './shared/client-class-transformer.js'; import * as b from '#compiler/builders';
import { get_name } from '../../../nodes.js';
import { regex_invalid_identifier_chars } from '../../../patterns.js';
import { get_rune } from '../../../scope.js';
import { should_proxy } from '../utils.js';
/** /**
* @param {ClassBody} node * @param {ClassBody} node
* @param {Context} context * @param {Context} context
*/ */
export function ClassBody(node, context) { export function ClassBody(node, context) {
if (!context.state.analysis.runes) { const state_fields = context.state.analysis.classes.get(node);
if (!state_fields) {
// in legacy mode, do nothing
context.next(); context.next();
return; return;
} }
const class_transformer = create_client_class_transformer(node.body); /** @type {string[]} */
const body = class_transformer.generate_body(context); 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);
}
}
const private_state = new Map();
/**
* each `foo = $state()` needs a backing `#foo` field
* @type {Record<string, PrivateIdentifier>}
*/
const backing_fields = {};
for (const name in state_fields) {
if (name[0] === '#') {
private_state.set(name.slice(1), state_fields[name]);
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>} */
const body = [];
const child_state = { ...context.state, state_fields, backing_fields, private_state }; // TODO populate private_state
for (const name in state_fields) {
if (name[0] === '#') {
continue;
}
const field = state_fields[name];
// 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 should_proxy = field.type === '$state' && true; // TODO
const key = b.key(name);
body.push(
b.prop_def(backing, null),
b.method('get', key, [], [b.return(b.call('$.get', member))]),
b.method(
'set',
key,
[b.id('value')],
[b.stmt(b.call('$.set', member, b.id('value'), should_proxy && b.true))]
)
);
}
}
// Replace parts of the class body
for (const definition of node.body) {
if (definition.type === 'MethodDefinition' || definition.type === 'StaticBlock') {
body.push(
/** @type {MethodDefinition | StaticBlock} */ (context.visit(definition, child_state))
);
continue;
}
const name = get_name(definition.key);
if (name === null || !Object.hasOwn(state_fields, name)) {
body.push(/** @type {PropertyDefinition} */ (context.visit(definition, child_state)));
continue;
}
const field = state_fields[name];
if (name[0] === '#') {
body.push(/** @type {PropertyDefinition} */ (context.visit(definition, child_state)));
} else {
const backing = backing_fields[name];
const member = b.member(b.this, backing);
const should_proxy = field.type === '$state' && true; // TODO
body.push(
b.prop_def(
backing,
/** @type {CallExpression} */ (
context.visit(definition.value ?? field.value, child_state)
)
),
b.method('get', definition.key, [], [b.return(b.call('$.get', member))]),
b.method(
'set',
definition.key,
[b.id('value')],
[b.stmt(b.call('$.set', member, b.id('value'), should_proxy && b.true))]
)
);
}
// if (definition.type === 'PropertyDefinition') {
// const original_name = get_name(definition.key);
// if (original_name === null) continue;
// const name = definition_names[original_name];
// const is_private = definition.key.type === 'PrivateIdentifier';
// const field = (is_private ? private_state : public_state).get(name);
// if (definition.value?.type === 'CallExpression' && field !== undefined) {
// let value = null;
// if (definition.value.arguments.length > 0) {
// const init = /** @type {Expression} **/ (
// context.visit(definition.value.arguments[0], child_state)
// );
// value =
// field.kind === 'state'
// ? b.call(
// '$.state',
// should_proxy(init, context.state.scope) ? b.call('$.proxy', init) : init
// )
// : field.kind === 'raw_state'
// ? b.call('$.state', init)
// : field.kind === 'derived_by'
// ? b.call('$.derived', init)
// : b.call('$.derived', b.thunk(init));
// } else {
// // if no arguments, we know it's state as `$derived()` is a compile error
// value = b.call('$.state');
// }
// 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('$.get', member))]));
// // set foo(value) { this.#foo = value; }
// const val = b.id('value');
// body.push(
// b.method(
// 'set',
// definition.key,
// [val],
// [b.stmt(b.call('$.set', member, val, field.kind === 'state' && b.true))]
// )
// );
// }
// continue;
// }
// }
// body.push(/** @type {MethodDefinition} **/ (context.visit(definition, child_state)));
}
return { ...node, body }; return { ...node, body };
} }
/**
* @param {string} name
* @param {Map<string, StateField>} public_state
*/
function get_deconflicted_name(name, public_state) {
name = name.replace(regex_invalid_identifier_chars, '_');
// the above could generate conflicts because it has to generate a valid identifier
// so stuff like `0` and `1` or `state%` and `state^` will result in the same string
// so we have to de-conflict. We can only check `public_state` because private state
// can't have literal keys
while (name && public_state.has(name)) {
name = '_' + name;
}
return name;
}

@ -9,10 +9,10 @@ import * as b from '#compiler/builders';
export function MemberExpression(node, context) { export function MemberExpression(node, context) {
// rewrite `this.#foo` as `this.#foo.v` inside a constructor // rewrite `this.#foo` as `this.#foo.v` inside a constructor
if (node.property.type === 'PrivateIdentifier') { if (node.property.type === 'PrivateIdentifier') {
const field = context.state.class_transformer?.get_field(node.property.name, true); const field = context.state.private_state.get(node.property.name);
if (field) { if (field) {
return context.state.in_constructor && return context.state.in_constructor &&
(field.kind === '$state.raw' || field.kind === '$state') (field.type === '$state.raw' || field.type === '$state')
? b.member(node, 'v') ? b.member(node, 'v')
: b.call('$.get', node); : b.call('$.get', node);
} }

@ -15,7 +15,7 @@ export function UpdateExpression(node, context) {
argument.type === 'MemberExpression' && argument.type === 'MemberExpression' &&
argument.object.type === 'ThisExpression' && argument.object.type === 'ThisExpression' &&
argument.property.type === 'PrivateIdentifier' && argument.property.type === 'PrivateIdentifier' &&
context.state.class_transformer?.get_field(argument.property.name, true) context.state.private_state.has(argument.property.name)
) { ) {
let fn = '$.update'; let fn = '$.update';
if (node.prefix) fn += '_pre'; if (node.prefix) fn += '_pre';

@ -99,7 +99,7 @@ export function server_component(analysis, options) {
template: /** @type {any} */ (null), template: /** @type {any} */ (null),
namespace: options.namespace, namespace: options.namespace,
preserve_whitespace: options.preserveWhitespace, preserve_whitespace: options.preserveWhitespace,
class_transformer: null, private_derived: new Map(),
skip_hydration_boundaries: false skip_hydration_boundaries: false
}; };
@ -395,7 +395,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(),
class_transformer: null private_derived: new Map()
}; };
const module = /** @type {Program} */ ( const module = /** @type {Program} */ (

@ -2,12 +2,12 @@ import type { Expression, Statement, ModuleDeclaration, LabeledStatement } from
import type { AST, Namespace, ValidatedCompileOptions } from '#compiler'; import type { AST, Namespace, ValidatedCompileOptions } from '#compiler';
import type { TransformState } from '../types.js'; import type { TransformState } from '../types.js';
import type { ComponentAnalysis } from '../../types.js'; import type { ComponentAnalysis } from '../../types.js';
import type { ClassTransformer } from '../shared/types.js'; import type { StateField } from '../client/types.js';
export interface ServerTransformState extends TransformState { export interface ServerTransformState extends TransformState {
/** The $: calls, which will be ordered in the end */ /** The $: calls, which will be ordered in the end */
readonly legacy_reactive_statements: Map<LabeledStatement, Statement>; readonly legacy_reactive_statements: Map<LabeledStatement, Statement>;
readonly class_transformer: ClassTransformer<Context> | null; readonly private_derived: Map<string, StateField>;
} }
export interface ComponentServerTransformState extends ServerTransformState { export interface ComponentServerTransformState extends ServerTransformState {

@ -3,6 +3,8 @@
/** @import { Context, ServerTransformState } from '../types.js' */ /** @import { Context, ServerTransformState } from '../types.js' */
import * as b from '#compiler/builders'; import * as b from '#compiler/builders';
import { build_assignment_value } from '../../../../utils/ast.js'; import { build_assignment_value } from '../../../../utils/ast.js';
import { get_name } from '../../../nodes.js';
import { get_rune } from '../../../scope.js';
import { visit_assignment_expression } from '../../shared/assignments.js'; import { visit_assignment_expression } from '../../shared/assignments.js';
/** /**
@ -10,11 +12,6 @@ import { visit_assignment_expression } from '../../shared/assignments.js';
* @param {Context} context * @param {Context} context
*/ */
export function AssignmentExpression(node, context) { export function AssignmentExpression(node, context) {
const stripped_node = context.state.class_transformer?.generate_assignment(node, context);
if (stripped_node) {
return stripped_node;
}
return visit_assignment_expression(node, context, build_assignment) ?? context.next(); return visit_assignment_expression(node, context, build_assignment) ?? context.next();
} }
@ -27,6 +24,30 @@ export function AssignmentExpression(node, context) {
* @returns {Expression | null} * @returns {Expression | null}
*/ */
function build_assignment(operator, left, right, context) { function build_assignment(operator, left, right, context) {
if (context.state.analysis.runes && left.type === 'MemberExpression') {
// special case — state declaration in class constructor
const ancestor = context.path.at(-4);
if (ancestor?.type === 'MethodDefinition' && ancestor.kind === 'constructor') {
const rune = get_rune(right, context.state.scope);
if (rune) {
const name = get_name(left.property);
const l = b.member(
b.this,
left.property.type === 'PrivateIdentifier' || rune === '$state' || rune === '$state.raw'
? left.property
: context.state.backing_fields[name]
);
const r = /** @type {Expression} */ (context.visit(right));
return b.assignment(operator, l, r);
}
}
}
let object = left; let object = left;
while (object.type === 'MemberExpression') { while (object.type === 'MemberExpression') {

@ -25,6 +25,15 @@ export function CallExpression(node, context) {
return b.arrow([], b.block([])); return b.arrow([], b.block([]));
} }
if (rune === '$state' || rune === '$state.raw') {
return node.arguments[0] ? context.visit(node.arguments[0]) : b.void0;
}
if (rune === '$derived' || rune === '$derived.by') {
const fn = /** @type {Expression} */ (context.visit(node.arguments[0]));
return b.call('$.once', rune === '$derived' ? b.thunk(fn) : fn);
}
if (rune === '$state.snapshot') { if (rune === '$state.snapshot') {
return b.call( return b.call(
'$.snapshot', '$.snapshot',

@ -1,19 +1,129 @@
/** @import { ClassBody } from 'estree' */ /** @import { CallExpression, ClassBody, Expression, MethodDefinition, PrivateIdentifier, PropertyDefinition, StaticBlock } from 'estree' */
/** @import { Context } from '../types.js' */ /** @import { Context } from '../types.js' */
import { create_server_class_transformer } from './shared/server-class-transformer.js'; /** @import { StateField } from '../../client/types.js' */
import { dev } from '../../../../state.js';
import * as b from '#compiler/builders';
import { get_rune } from '../../../scope.js';
import { regex_invalid_identifier_chars } from '../../../patterns.js';
import { get_name } from '../../../nodes.js';
/** /**
* @param {ClassBody} node * @param {ClassBody} node
* @param {Context} context * @param {Context} context
*/ */
export function ClassBody(node, context) { export function ClassBody(node, context) {
if (!context.state.analysis.runes) { const state_fields = context.state.analysis.classes.get(node);
if (!state_fields) {
// in legacy mode, do nothing
context.next(); context.next();
return; return;
} }
const class_transformer = create_server_class_transformer(node.body); /** @type {string[]} */
const body = class_transformer.generate_body(context); 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);
}
}
const private_state = new Map();
/**
* each `foo = $state()` needs a backing `#foo` field
* @type {Record<string, PrivateIdentifier>}
*/
const backing_fields = {};
for (const name in state_fields) {
if (name[0] === '#') {
private_state.set(name.slice(1), state_fields[name]);
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>} */
const body = [];
const child_state = { ...context.state, state_fields, backing_fields, private_state }; // TODO populate private_state
for (const name in state_fields) {
if (name[0] === '#') {
continue;
}
const field = state_fields[name];
// insert backing fields for stuff declared in the constructor
if (
field.node.type === 'AssignmentExpression' &&
(field.type === '$derived' || field.type === '$derived.by')
) {
const backing = backing_fields[name];
const member = b.member(b.this, backing);
const should_proxy = field.type === '$state' && true; // TODO
const key = b.key(name);
body.push(
b.prop_def(backing, null),
b.method('get', key, [], [b.return(b.call(member))])
);
}
}
// Replace parts of the class body
for (const definition of node.body) {
if (definition.type === 'MethodDefinition' || definition.type === 'StaticBlock') {
body.push(
/** @type {MethodDefinition | StaticBlock} */ (context.visit(definition, child_state))
);
continue;
}
const name = get_name(definition.key);
if (name === null || !Object.hasOwn(state_fields, name)) {
body.push(/** @type {PropertyDefinition} */ (context.visit(definition, child_state)));
continue;
}
const field = state_fields[name];
if (name[0] === '#' || field.type === '$state' || field.type === '$state.raw') {
body.push(/** @type {PropertyDefinition} */ (context.visit(definition, child_state)));
} else {
const backing = backing_fields[name];
const member = b.member(b.this, backing);
const should_proxy = field.type === '$state' && true; // TODO
body.push(
b.prop_def(
backing,
/** @type {CallExpression} */ (
context.visit(definition.value ?? field.value, child_state)
)
),
b.method('get', definition.key, [], [b.return(b.call(member))])
);
}
}
return { ...node, body }; return { ...node, body };
} }

@ -12,10 +12,7 @@ export function MemberExpression(node, context) {
node.object.type === 'ThisExpression' && node.object.type === 'ThisExpression' &&
node.property.type === 'PrivateIdentifier' node.property.type === 'PrivateIdentifier'
) { ) {
const field = context.state.class_transformer?.get_field(node.property.name, true, [ const field = context.state.private_derived.get(node.property.name);
'$derived',
'$derived.by'
]);
if (field) { if (field) {
return b.call(node); return b.call(node);

@ -1,8 +1,6 @@
import type { Scope } from '../scope.js'; import type { Scope } from '../scope.js';
import type { AST, ValidatedModuleCompileOptions } from '#compiler'; import type { AST, ValidatedModuleCompileOptions } from '#compiler';
import type { Analysis } from '../types.js'; import type { Analysis } from '../types.js';
import type { StateCreationRuneName } from '../../../utils.js';
import type { PrivateIdentifier } from 'estree';
export interface TransformState { export interface TransformState {
readonly analysis: Analysis; readonly analysis: Analysis;
@ -10,8 +8,3 @@ export interface TransformState {
readonly scope: Scope; readonly scope: Scope;
readonly scopes: Map<AST.SvelteNode, Scope>; readonly scopes: Map<AST.SvelteNode, Scope>;
} }
export interface StateField {
kind: StateCreationRuneName;
id: PrivateIdentifier;
}

@ -1,3 +1,4 @@
/** @import { Expression, PrivateIdentifier } from 'estree' */
/** @import { AST, ExpressionMetadata } from '#compiler' */ /** @import { AST, ExpressionMetadata } from '#compiler' */
/** /**
@ -65,3 +66,14 @@ export function create_expression_metadata() {
has_call: false has_call: false
}; };
} }
/**
* @param {Expression | PrivateIdentifier} node
*/
export function get_name(node) {
if (node.type === 'Literal') return String(node.value);
if (node.type === 'PrivateIdentifier') return '#' + node.name;
if (node.type === 'Identifier') return node.name;
return null;
}

@ -3,7 +3,7 @@ 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, PropertyDefinition } from 'estree'; import type { AssignmentExpression, CallExpression, 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 +274,7 @@ export interface ExpressionMetadata {
export interface StateField { export interface StateField {
type: StateCreationRuneName; type: StateCreationRuneName;
node: PropertyDefinition | AssignmentExpression; node: PropertyDefinition | AssignmentExpression;
value: CallExpression;
} }
export * from './template.js'; export * from './template.js';

Loading…
Cancel
Save