1
- /** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Pattern, Property, SequenceExpression, Statement } from 'estree' */
1
+ /** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Pattern, Property, SequenceExpression, Statement, SpreadElement } from 'estree' */
2
2
/** @import { AST } from '#compiler' */
3
3
/** @import { ComponentContext } from '../../types.js' */
4
4
import { dev , is_ignored } from '../../../../../state.js' ;
@@ -8,6 +8,7 @@ import { add_svelte_meta, build_bind_this, Memoizer, validate_binding } from '..
8
8
import { build_attribute_value } from '../shared/element.js' ;
9
9
import { build_event_handler } from './events.js' ;
10
10
import { determine_slot } from '../../../../../utils/slot.js' ;
11
+ import { init_spread_bindings } from '../../../shared/spread_bindings.js' ;
11
12
12
13
/**
13
14
* @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf } node
@@ -48,7 +49,7 @@ export function build_component(node, component_name, context) {
48
49
/** @type {Property[] } */
49
50
const custom_css_props = [ ] ;
50
51
51
- /** @type {Identifier | MemberExpression | SequenceExpression | null } */
52
+ /** @type {Identifier | MemberExpression | SequenceExpression | SpreadElement | null } */
52
53
let bind_this = null ;
53
54
54
55
/** @type {ExpressionStatement[] } */
@@ -196,84 +197,100 @@ export function build_component(node, component_name, context) {
196
197
push_prop ( b . init ( attribute . name , value ) ) ;
197
198
}
198
199
} else if ( attribute . type === 'BindDirective' ) {
199
- const expression = /** @type {Expression } */ ( context . visit ( attribute . expression ) ) ;
200
+ if ( attribute . expression . type === 'SpreadElement' ) {
201
+ const { get, set } = init_spread_bindings ( attribute . expression , context ) ;
200
202
201
- if (
202
- dev &&
203
- attribute . name !== 'this' &&
204
- ! is_ignored ( node , 'ownership_invalid_binding' ) &&
205
- // bind:x={() => x.y, y => x.y = y} will be handled by the assignment expression binding validation
206
- attribute . expression . type !== 'SequenceExpression'
207
- ) {
208
- const left = object ( attribute . expression ) ;
209
- const binding = left && context . state . scope . get ( left . name ) ;
210
-
211
- if ( binding ?. kind === 'bindable_prop' || binding ?. kind === 'prop' ) {
212
- context . state . analysis . needs_mutation_validation = true ;
213
- binding_initializers . push (
214
- b . stmt (
215
- b . call (
216
- '$$ownership_validator.binding' ,
217
- b . literal ( binding . node . name ) ,
218
- b . id ( is_component_dynamic ? intermediate_name : component_name ) ,
219
- b . thunk ( expression )
220
- )
221
- )
222
- ) ;
223
- }
224
- }
225
-
226
- if ( expression . type === 'SequenceExpression' ) {
227
203
if ( attribute . name === 'this' ) {
228
204
bind_this = attribute . expression ;
229
205
} else {
230
- const [ get , set ] = expression . expressions ;
231
- const get_id = b . id ( context . state . scope . generate ( 'bind_get' ) ) ;
232
- const set_id = b . id ( context . state . scope . generate ( 'bind_set' ) ) ;
233
-
234
- context . state . init . push ( b . var ( get_id , get ) ) ;
235
- context . state . init . push ( b . var ( set_id , set ) ) ;
236
-
237
- push_prop ( b . get ( attribute . name , [ b . return ( b . call ( get_id ) ) ] ) ) ;
238
- push_prop ( b . set ( attribute . name , [ b . stmt ( b . call ( set_id , b . id ( '$$value' ) ) ) ] ) ) ;
206
+ push_prop ( b . get ( attribute . name , [ b . return ( b . call ( get ) ) ] ) , true ) ;
207
+ push_prop ( b . set ( attribute . name , [ b . stmt ( b . call ( set , b . id ( '$$value' ) ) ) ] ) , true ) ;
239
208
}
240
209
} else {
210
+ const expression = /** @type {Expression } */ ( context . visit ( attribute . expression ) ) ;
211
+
241
212
if (
242
213
dev &&
243
- expression . type === 'MemberExpression' &&
244
- context . state . analysis . runes &&
245
- ! is_ignored ( node , 'binding_property_non_reactive' )
214
+ attribute . name !== 'this' &&
215
+ ! is_ignored ( node , 'ownership_invalid_binding' ) &&
216
+ // bind:x={() => x.y, y => x.y = y} will be handled by the assignment expression binding validation
217
+ attribute . expression . type !== 'SequenceExpression'
246
218
) {
247
- validate_binding ( context . state , attribute , expression ) ;
219
+ const left = object ( attribute . expression ) ;
220
+ const binding = left && context . state . scope . get ( left . name ) ;
221
+
222
+ if ( binding ?. kind === 'bindable_prop' || binding ?. kind === 'prop' ) {
223
+ context . state . analysis . needs_mutation_validation = true ;
224
+ binding_initializers . push (
225
+ b . stmt (
226
+ b . call (
227
+ '$$ownership_validator.binding' ,
228
+ b . literal ( binding . node . name ) ,
229
+ b . id ( is_component_dynamic ? intermediate_name : component_name ) ,
230
+ b . thunk ( expression )
231
+ )
232
+ )
233
+ ) ;
234
+ }
248
235
}
249
236
250
- if ( attribute . name === 'this' ) {
251
- bind_this = attribute . expression ;
237
+ if ( expression . type === 'SequenceExpression' ) {
238
+ if ( attribute . name === 'this' ) {
239
+ bind_this = attribute . expression ;
240
+ } else {
241
+ const [ get , set ] = expression . expressions ;
242
+ const get_id = b . id ( context . state . scope . generate ( 'bind_get' ) ) ;
243
+ const set_id = b . id ( context . state . scope . generate ( 'bind_set' ) ) ;
244
+
245
+ context . state . init . push ( b . var ( get_id , get ) ) ;
246
+ context . state . init . push ( b . var ( set_id , set ) ) ;
247
+
248
+ push_prop ( b . get ( attribute . name , [ b . return ( b . call ( get_id ) ) ] ) ) ;
249
+ push_prop ( b . set ( attribute . name , [ b . stmt ( b . call ( set_id , b . id ( '$$value' ) ) ) ] ) ) ;
250
+ }
252
251
} else {
253
- const is_store_sub =
254
- attribute . expression . type === 'Identifier' &&
255
- context . state . scope . get ( attribute . expression . name ) ?. kind === 'store_sub' ;
252
+ if (
253
+ dev &&
254
+ expression . type === 'MemberExpression' &&
255
+ context . state . analysis . runes &&
256
+ ! is_ignored ( node , 'binding_property_non_reactive' )
257
+ ) {
258
+ validate_binding ( context . state , attribute , expression ) ;
259
+ }
260
+
261
+ if ( attribute . name === 'this' ) {
262
+ bind_this = attribute . expression ;
263
+ } else {
264
+ const is_store_sub =
265
+ attribute . expression . type === 'Identifier' &&
266
+ context . state . scope . get ( attribute . expression . name ) ?. kind === 'store_sub' ;
267
+
268
+ // Delay prop pushes so bindings come at the end, to avoid spreads overwriting them
269
+ if ( is_store_sub ) {
270
+ push_prop (
271
+ b . get ( attribute . name , [
272
+ b . stmt ( b . call ( '$.mark_store_binding' ) ) ,
273
+ b . return ( expression )
274
+ ] ) ,
275
+ true
276
+ ) ;
277
+ } else {
278
+ push_prop ( b . get ( attribute . name , [ b . return ( expression ) ] ) , true ) ;
279
+ }
280
+
281
+ const assignment = b . assignment (
282
+ '=' ,
283
+ /** @type {Pattern } */ ( attribute . expression ) ,
284
+ b . id ( '$$value' )
285
+ ) ;
256
286
257
- // Delay prop pushes so bindings come at the end, to avoid spreads overwriting them
258
- if ( is_store_sub ) {
259
287
push_prop (
260
- b . get ( attribute . name , [ b . stmt ( b . call ( '$.mark_store_binding' ) ) , b . return ( expression ) ] ) ,
288
+ b . set ( attribute . name , [
289
+ b . stmt ( /** @type {Expression } */ ( context . visit ( assignment ) ) )
290
+ ] ) ,
261
291
true
262
292
) ;
263
- } else {
264
- push_prop ( b . get ( attribute . name , [ b . return ( expression ) ] ) , true ) ;
265
293
}
266
-
267
- const assignment = b . assignment (
268
- '=' ,
269
- /** @type {Pattern } */ ( attribute . expression ) ,
270
- b . id ( '$$value' )
271
- ) ;
272
-
273
- push_prop (
274
- b . set ( attribute . name , [ b . stmt ( /** @type {Expression } */ ( context . visit ( assignment ) ) ) ] ) ,
275
- true
276
- ) ;
277
294
}
278
295
}
279
296
} else if ( attribute . type === 'AttachTag' ) {
0 commit comments