1- /** @import  { ObjectExpression, Property, CallExpression, Expression, SpreadElement } from 'estree' */ 
1+ /** @import  { ObjectExpression, Property, CallExpression, Expression, SpreadElement, Node, Identifier, PrivateIdentifier, Statement  } from 'estree' */ 
22/** @import  { Context } from '../types' */ 
33import  *  as  b  from  '../../../../utils/builders.js' ; 
44import  {  get_rune  }  from  '../../../scope.js' ; 
@@ -10,12 +10,15 @@ import { walk } from 'zimmerframe';
1010 * @param  {Context } context 
1111 */ 
1212export  function  ObjectExpression ( node ,  context )  { 
13+ 	/** 
14+ 	 * @typedef  {[string, NonNullable<ReturnType<typeof get_rune>>, '$.state' | '$.derived', Expression, boolean] } ReactiveProperty 
15+ 	 */ 
1316	let  has_runes  =  false ; 
1417	/** 
1518	 * @type  {Array<{rune: NonNullable<ReturnType<typeof get_rune>>, property: Property & {value: CallExpression}}> } 
1619	 */ 
17- 	let  reactive_properties  =  [ ] ; 
18- 	let  valid_property_runes  =  [ '$state' ,  '$derived' ,  '$state.raw' ,  '$derived.by' ] ; 
20+ 	const  reactive_properties  =  [ ] ; 
21+ 	const  valid_property_runes  =  [ '$state' ,  '$derived' ,  '$state.raw' ,  '$derived.by' ] ; 
1922	for  ( let  property  of  node . properties )  { 
2023		if  ( property . type  !==  'Property' )  continue ; 
2124		const  rune  =  get_rune ( property . value ,  context . state . scope ) ; 
@@ -31,14 +34,61 @@ export function ObjectExpression(node, context) {
3134		context . next ( ) ; 
3235		return ; 
3336	} 
34- 	let  body  =  [ ] ; 
35- 	let  sources  =  new  Map ( ) ; 
37+ 	/** @type  {Statement[] } */ 
38+ 	const  body  =  [ ] ; 
39+ 	/** @type  {Map<Property, ReactiveProperty> } */ 
40+ 	const  sources  =  new  Map ( ) ; 
3641	let  has_this_reference  =  false ; 
3742	let  counter  =  0 ; 
38- 	let  to_push  =  [ ] ; 
43+ 	/** @type  {Statement[] } */ 
44+ 	const  before  =  [ ] ; 
45+ 	/** @type  {Statement[] } */ 
46+ 	const  after  =  [ ] ; 
47+ 	/** @type  {string[] } */ 
48+ 	const  declarations  =  [ ] ; 
49+ 	/** @type  {Map<string, Expression | undefined> } */ 
50+ 	const  initial_declarations  =  new  Map ( ) ; 
51+ 	// if a computed property is accessed, we treat it as if all of the object's properties have been accessed 
52+ 	let  all_are_referenced  =  false ; 
53+ 	/** @type  {Set<any> } */ 
54+ 	const  is_referenced  =  new  Set ( ) ; 
55+ 	for  ( let  property  of  node . properties )  { 
56+ 		walk ( property ,  null ,  { 
57+ 			//@ts -ignore 
58+ 			FunctionExpression ( )  { 
59+ 				return ; 
60+ 			} , 
61+ 			//@ts -ignore 
62+ 			FunctionDeclaration ( )  { 
63+ 				return ; 
64+ 			} , 
65+ 			ObjectExpression ( )  { 
66+ 				return ; 
67+ 			} , 
68+ 			/** 
69+ 			 * 
70+ 			 * @param  {Node } node 
71+ 			 * @param  {import('zimmerframe').Context<Node, null> } context 
72+ 			 */ 
73+ 			ThisExpression ( node ,  context )  { 
74+ 				const  parent  =  context . path . at ( - 1 ) ; 
75+ 				if  ( parent ?. type  ===  'MemberExpression' )  { 
76+ 					if  ( parent . computed )  { 
77+ 						all_are_referenced  =  true ; 
78+ 					}  else  { 
79+ 						is_referenced . add ( /** @type  {Identifier | PrivateIdentifier } */  ( parent . property ) . name ) ; 
80+ 					} 
81+ 				} 
82+ 			} , 
83+ 			ClassBody ( )  { 
84+ 				return ; 
85+ 			} 
86+ 		} ) ; 
87+ 	} 
3988	for  ( let  {  rune,  property }  of  reactive_properties )  { 
4089		const  name  =  context . state . scope . generate ( `$$${ ++ counter }  ` ) ; 
4190		const  call  =  rune . match ( / ^ \$ s t a t e / )  ? '$.state'  : '$.derived' ; 
91+ 		let  references_this  =  false ; 
4292		/** @type  {Expression } */ 
4393		let  value  =  /** @type  {Expression } */  ( context . visit ( property . value . arguments [ 0 ]  ??  b . void0 ) ) ; 
4494		value  =  walk ( value ,  null ,  { 
@@ -54,6 +104,7 @@ export function ObjectExpression(node, context) {
54104			} , 
55105			ThisExpression ( )  { 
56106				has_this_reference  =  true ; 
107+ 				references_this  =  true ; 
57108				return  b . id ( '$$object' ) ; 
58109			} , 
59110			ClassBody ( )  { 
@@ -66,23 +117,76 @@ export function ObjectExpression(node, context) {
66117				: rune  ===  '$state'  &&  should_proxy ( value ,  context . state . scope ) 
67118					? b . call ( '$.proxy' ,  value ) 
68119					: value ; 
69- 		sources . set ( property ,  [ name ,  rune ] ) ; 
70- 		to_push . push ( b . let ( name ,  b . call ( call ,  value ) ) ) ; 
120+ 		let  key  =  property . computed 
121+ 			? Symbol ( ) 
122+ 			: property . key . type  ===  'Literal' 
123+ 				? property . key . value 
124+ 				: /** @type  {Identifier } */  ( property . key ) . name ; 
125+ 		if  ( rune . match ( / ^ \$ s t a t e / )  &&  ! ( all_are_referenced  ||  is_referenced . has ( key ) ) )  { 
126+ 			let  should_be_declared  =  false ; 
127+ 			walk ( value ,  null ,  { 
128+ 				CallExpression ( node ,  context )  { 
129+ 					should_be_declared  =  true ; 
130+ 					context . stop ( ) ; 
131+ 				} , 
132+ 				MemberExpression ( node ,  context )  { 
133+ 					should_be_declared  =  true ; 
134+ 					context . stop ( ) ; 
135+ 				} 
136+ 			} ) ; 
137+ 			if  ( should_be_declared )  { 
138+ 				const  value_name  =  context . state . scope . generate ( '$$initial' ) ; 
139+ 				initial_declarations . set ( value_name ,  value ) ; 
140+ 				value  =  b . id ( value_name ) ; 
141+ 			} 
142+ 		} 
143+ 		/** @type  {ReactiveProperty } */ 
144+ 		const  source  =  [ 
145+ 			name , 
146+ 			rune , 
147+ 			call , 
148+ 			value , 
149+ 			( value . type  ===  'Identifier'  &&  initial_declarations . has ( value . name ) )  ||  references_this 
150+ 		] ; 
151+ 		sources . set ( property ,  source ) ; 
152+ 		if  ( references_this )  { 
153+ 			declarations . push ( name ) ; 
154+ 		}  else  if  ( source [ 4 ] )  { 
155+ 			before . push ( b . let ( name ,  value ) ) ; 
156+ 		}  else  { 
157+ 			before . push ( b . let ( name ,  b . call ( call ,  value ) ) ) ; 
158+ 		} 
159+ 	} 
160+ 	if  ( declarations . length )  { 
161+ 		before . push ( 
162+ 			b . declaration ( 
163+ 				'let' , 
164+ 				declarations . map ( ( name )  =>  b . declarator ( name ) ) 
165+ 			) 
166+ 		) ; 
167+ 	} 
168+ 	for  ( let  [ name ,  value ]  of  initial_declarations )  { 
169+ 		after . push ( b . let ( name ,  value ) ) ; 
71170	} 
72171	/** @type  {(Property | SpreadElement)[] } */ 
73- 	let  properties  =  [ ] ; 
172+ 	const  properties  =  [ ] ; 
74173	for  ( let  property  of  node . properties )  { 
75174		if  ( property . type  ===  'SpreadElement' )  { 
76175			properties . push ( /** @type  {SpreadElement } */  ( context . visit ( property ) ) ) ; 
77176			continue ; 
78177		} 
79178		if  ( sources . has ( property ) )  { 
80- 			let  [ name ,  rune ]  =  sources . get ( property ) ; 
179+ 			const  [ name ,  rune ,  call ,  value ,  initially_declared ]  =  /** @type  {ReactiveProperty } */  ( 
180+ 				sources . get ( property ) 
181+ 			) ; 
182+ 			let  maybe_assign  =  initially_declared 
183+ 				? b . assignment ( '??=' ,  b . id ( name ) ,  b . call ( call ,  value ) ) 
184+ 				: b . id ( name ) ; 
81185			properties . push ( 
82186				b . prop ( 
83187					'get' , 
84188					/** @type  {Expression } */  ( context . visit ( /**@type  {Expression } */  ( property . key ) ) ) , 
85- 					b . function ( null ,  [ ] ,  b . block ( [ b . return ( b . call ( '$.get' ,  b . id ( name ) ) ) ] ) ) , 
189+ 					b . function ( null ,  [ ] ,  b . block ( [ b . return ( b . call ( '$.get' ,  maybe_assign ) ) ] ) ) , 
86190					property . computed 
87191				) , 
88192				b . prop ( 
@@ -93,7 +197,12 @@ export function ObjectExpression(node, context) {
93197						[ b . id ( '$$value' ) ] , 
94198						b . block ( [ 
95199							b . stmt ( 
96- 								b . call ( '$.set' ,  b . id ( name ) ,  b . id ( '$$value' ) ,  rune  ===  '$state'  ? b . true  : undefined ) 
200+ 								b . call ( 
201+ 									'$.set' , 
202+ 									maybe_assign , 
203+ 									b . id ( '$$value' ) , 
204+ 									rune  ===  '$state'  ? b . true  : undefined 
205+ 								) 
97206							) 
98207						] ) 
99208					) , 
@@ -105,11 +214,12 @@ export function ObjectExpression(node, context) {
105214		} 
106215	} 
107216	if  ( has_this_reference )  { 
217+ 		body . push ( ...before ) ; 
108218		body . push ( b . let ( '$$object' ,  b . object ( properties ) ) ) ; 
109- 		body . push ( ...to_push ) ; 
219+ 		body . push ( ...after ) ; 
110220		body . push ( b . return ( b . id ( '$$object' ) ) ) ; 
111221	}  else  { 
112- 		body . push ( ...to_push ) ; 
222+ 		body . push ( ...before ,  ... after ) ; 
113223		body . push ( b . return ( b . object ( properties ) ) ) ; 
114224	} 
115225	return  b . call ( b . arrow ( [ ] ,  b . block ( body ) ) ) ; 
0 commit comments