1- /** @import { ClassBody, Expression, Identifier, Literal, MethodDefinition, PrivateIdentifier, PropertyDefinition } from 'estree' */
1+ /** @import { ClassBody, Identifier, Literal, MethodDefinition, PrivateIdentifier, PropertyDefinition } from 'estree' */
22/** @import { Context, StateField } from '../types' */
3- import * as b from '#compiler/builders' ;
43import { regex_invalid_identifier_chars } from '../../../patterns.js' ;
5- import { get_rune } from '../../../scope.js' ;
6- import { should_proxy } from '../utils.js' ;
4+ import { ClassAnalysis } from './shared/class-analysis.js' ;
75
86/**
97 * @param {ClassBody } node
@@ -15,170 +13,46 @@ export function ClassBody(node, context) {
1513 return ;
1614 }
1715
18- /** @type {Map<string, StateField> } */
19- const public_state = new Map ( ) ;
20-
21- /** @type {Map<string, StateField> } */
22- const private_state = new Map ( ) ;
23-
24- /** @type {Map<(MethodDefinition|PropertyDefinition)["key"], string> } */
25- const definition_names = new Map ( ) ;
26-
27- /** @type {string[] } */
28- const private_ids = [ ] ;
16+ const class_analysis = new ClassAnalysis ( ) ;
2917
3018 for ( const definition of node . body ) {
31- if (
32- ( definition . type === 'PropertyDefinition' || definition . type === 'MethodDefinition' ) &&
33- ( definition . key . type === 'Identifier' ||
34- definition . key . type === 'PrivateIdentifier' ||
35- definition . key . type === 'Literal' )
36- ) {
37- const type = definition . key . type ;
38- const name = get_name ( definition . key , public_state ) ;
39- if ( ! name ) continue ;
40-
41- // we store the deconflicted name in the map so that we can access it later
42- definition_names . set ( definition . key , name ) ;
43-
44- const is_private = type === 'PrivateIdentifier' ;
45- if ( is_private ) private_ids . push ( name ) ;
46-
47- if ( definition . value ?. type === 'CallExpression' ) {
48- const rune = get_rune ( definition . value , context . state . scope ) ;
49- if (
50- rune === '$state' ||
51- rune === '$state.raw' ||
52- rune === '$derived' ||
53- rune === '$derived.by'
54- ) {
55- /** @type {StateField } */
56- const field = {
57- kind :
58- rune === '$state'
59- ? 'state'
60- : rune === '$state.raw'
61- ? 'raw_state'
62- : rune === '$derived.by'
63- ? 'derived_by'
64- : 'derived' ,
65- // @ts -expect-error this is set in the next pass
66- id : is_private ? definition . key : null
67- } ;
68-
69- if ( is_private ) {
70- private_state . set ( name , field ) ;
71- } else {
72- public_state . set ( name , field ) ;
73- }
74- }
75- }
76- }
19+ class_analysis . register_body_definition ( definition , context . state . scope ) ;
7720 }
7821
79- // each `foo = $state()` needs a backing `#foo` field
80- for ( const [ name , field ] of public_state ) {
81- let deconflicted = name ;
82- while ( private_ids . includes ( deconflicted ) ) {
83- deconflicted = '_' + deconflicted ;
84- }
85-
86- private_ids . push ( deconflicted ) ;
87- field . id = b . private_id ( deconflicted ) ;
88- }
22+ class_analysis . finalize_property_definitions ( ) ;
8923
9024 /** @type {Array<MethodDefinition | PropertyDefinition> } */
9125 const body = [ ] ;
9226
93- const child_state = { ...context . state , public_state, private_state } ;
27+ const child_state = {
28+ ...context . state ,
29+ class_analysis
30+ } ;
31+
32+ // we need to visit the constructor first so that it can add to the field maps.
33+ const constructor_node = node . body . find (
34+ ( child ) => child . type === 'MethodDefinition' && child . kind === 'constructor'
35+ ) ;
36+ const constructor = constructor_node && context . visit ( constructor_node , child_state ) ;
9437
9538 // Replace parts of the class body
9639 for ( const definition of node . body ) {
97- if (
98- definition . type === 'PropertyDefinition' &&
99- ( definition . key . type === 'Identifier' ||
100- definition . key . type === 'PrivateIdentifier' ||
101- definition . key . type === 'Literal' )
102- ) {
103- const name = definition_names . get ( definition . key ) ;
104- if ( ! name ) continue ;
105-
106- const is_private = definition . key . type === 'PrivateIdentifier' ;
107- const field = ( is_private ? private_state : public_state ) . get ( name ) ;
108-
109- if ( definition . value ?. type === 'CallExpression' && field !== undefined ) {
110- let value = null ;
111-
112- if ( definition . value . arguments . length > 0 ) {
113- const init = /** @type {Expression } **/ (
114- context . visit ( definition . value . arguments [ 0 ] , child_state )
115- ) ;
116-
117- value =
118- field . kind === 'state'
119- ? b . call (
120- '$.state' ,
121- should_proxy ( init , context . state . scope ) ? b . call ( '$.proxy' , init ) : init
122- )
123- : field . kind === 'raw_state'
124- ? b . call ( '$.state' , init )
125- : field . kind === 'derived_by'
126- ? b . call ( '$.derived' , init )
127- : b . call ( '$.derived' , b . thunk ( init ) ) ;
128- } else {
129- // if no arguments, we know it's state as `$derived()` is a compile error
130- value = b . call ( '$.state' ) ;
131- }
132-
133- if ( is_private ) {
134- body . push ( b . prop_def ( field . id , value ) ) ;
135- } else {
136- // #foo;
137- const member = b . member ( b . this , field . id ) ;
138- body . push ( b . prop_def ( field . id , value ) ) ;
139-
140- // get foo() { return this.#foo; }
141- body . push ( b . method ( 'get' , definition . key , [ ] , [ b . return ( b . call ( '$.get' , member ) ) ] ) ) ;
40+ if ( definition === constructor_node ) {
41+ body . push ( /** @type {MethodDefinition } */ ( constructor ) ) ;
42+ continue ;
43+ }
14244
143- // set foo(value) { this.#foo = value; }
144- const val = b . id ( 'value' ) ;
45+ const state_field = class_analysis . build_state_field_from_body_definition ( definition , context ) ;
14546
146- body . push (
147- b . method (
148- 'set' ,
149- definition . key ,
150- [ val ] ,
151- [ b . stmt ( b . call ( '$.set' , member , val , field . kind === 'state' && b . true ) ) ]
152- )
153- ) ;
154- }
155- continue ;
156- }
47+ if ( state_field ) {
48+ body . push ( ...state_field ) ;
49+ continue ;
15750 }
15851
15952 body . push ( /** @type {MethodDefinition } **/ ( context . visit ( definition , child_state ) ) ) ;
16053 }
16154
162- return { ...node , body } ;
163- }
55+ body . push ( ...class_analysis . constructor_state_fields ) ;
16456
165- /**
166- * @param {Identifier | PrivateIdentifier | Literal } node
167- * @param {Map<string, StateField> } public_state
168- */
169- function get_name ( node , public_state ) {
170- if ( node . type === 'Literal' ) {
171- let name = node . value ?. toString ( ) . replace ( regex_invalid_identifier_chars , '_' ) ;
172-
173- // the above could generate conflicts because it has to generate a valid identifier
174- // so stuff like `0` and `1` or `state%` and `state^` will result in the same string
175- // so we have to de-conflict. We can only check `public_state` because private state
176- // can't have literal keys
177- while ( name && public_state . has ( name ) ) {
178- name = '_' + name ;
179- }
180- return name ;
181- } else {
182- return node . name ;
183- }
57+ return { ...node , body } ;
18458}
0 commit comments