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' */
22/** @import { AST } from '#compiler' */
33/** @import { ComponentContext } from '../../types.js' */
44import { dev , is_ignored } from '../../../../../state.js' ;
@@ -8,6 +8,7 @@ import { add_svelte_meta, build_bind_this, Memoizer, validate_binding } from '..
88import { build_attribute_value } from '../shared/element.js' ;
99import { build_event_handler } from './events.js' ;
1010import { determine_slot } from '../../../../../utils/slot.js' ;
11+ import { init_spread_bindings } from '../../../shared/spread_bindings.js' ;
1112
1213/**
1314 * @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf } node
@@ -48,7 +49,7 @@ export function build_component(node, component_name, context) {
4849 /** @type {Property[] } */
4950 const custom_css_props = [ ] ;
5051
51- /** @type {Identifier | MemberExpression | SequenceExpression | null } */
52+ /** @type {Identifier | MemberExpression | SequenceExpression | SpreadElement | null } */
5253 let bind_this = null ;
5354
5455 /** @type {ExpressionStatement[] } */
@@ -196,84 +197,100 @@ export function build_component(node, component_name, context) {
196197 push_prop ( b . init ( attribute . name , value ) ) ;
197198 }
198199 } 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 ) ;
200202
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' ) {
227203 if ( attribute . name === 'this' ) {
228204 bind_this = attribute . expression ;
229205 } 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 ) ;
239208 }
240209 } else {
210+ const expression = /** @type {Expression } */ ( context . visit ( attribute . expression ) ) ;
211+
241212 if (
242213 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'
246218 ) {
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+ }
248235 }
249236
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+ }
252251 } 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+ ) ;
256286
257- // Delay prop pushes so bindings come at the end, to avoid spreads overwriting them
258- if ( is_store_sub ) {
259287 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+ ] ) ,
261291 true
262292 ) ;
263- } else {
264- push_prop ( b . get ( attribute . name , [ b . return ( expression ) ] ) , true ) ;
265293 }
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- ) ;
277294 }
278295 }
279296 } else if ( attribute . type === 'AttachTag' ) {
0 commit comments