1
- /** @import { ClassBody, Expression, Identifier, Literal, MethodDefinition, PrivateIdentifier, PropertyDefinition } from 'estree' */
1
+ /** @import { ClassBody, Identifier, Literal, MethodDefinition, PrivateIdentifier, PropertyDefinition } from 'estree' */
2
2
/** @import { Context, StateField } from '../types' */
3
- import * as b from '#compiler/builders' ;
4
3
import { 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' ;
7
5
8
6
/**
9
7
* @param {ClassBody } node
@@ -15,170 +13,46 @@ export function ClassBody(node, context) {
15
13
return ;
16
14
}
17
15
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 ( ) ;
29
17
30
18
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 ) ;
77
20
}
78
21
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 ( ) ;
89
23
90
24
/** @type {Array<MethodDefinition | PropertyDefinition> } */
91
25
const body = [ ] ;
92
26
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 ) ;
94
37
95
38
// Replace parts of the class body
96
39
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
+ }
142
44
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 ) ;
145
46
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 ;
157
50
}
158
51
159
52
body . push ( /** @type {MethodDefinition } **/ ( context . visit ( definition , child_state ) ) ) ;
160
53
}
161
54
162
- return { ...node , body } ;
163
- }
55
+ body . push ( ...class_analysis . constructor_state_fields ) ;
164
56
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 } ;
184
58
}
0 commit comments