@ -17,102 +17,6 @@ import { is_content_editable_binding, is_svg } from '../../../../utils.js';
* @ param { Context } context
* /
export function BindDirective ( node , context ) {
validate _no _const _assignment ( node , node . expression , context . state . scope , true ) ;
const assignee = node . expression ;
const left = object ( assignee ) ;
if ( left === null ) {
e . bind _invalid _expression ( node ) ;
}
const binding = context . state . scope . get ( left . name ) ;
if ( assignee . type === 'Identifier' ) {
// reassignment
if (
node . name !== 'this' && // bind:this also works for regular variables
( ! binding ||
( binding . kind !== 'state' &&
binding . kind !== 'raw_state' &&
binding . kind !== 'prop' &&
binding . kind !== 'bindable_prop' &&
binding . kind !== 'each' &&
binding . kind !== 'store_sub' &&
! binding . updated ) ) // TODO wut?
) {
e . bind _invalid _value ( node . expression ) ;
}
if ( context . state . analysis . runes && binding ? . kind === 'each' ) {
e . each _item _invalid _assignment ( node ) ;
}
if ( binding ? . kind === 'snippet' ) {
e . snippet _parameter _assignment ( node ) ;
}
}
if ( node . name === 'group' ) {
if ( ! binding ) {
throw new Error ( 'Cannot find declaration for bind:group' ) ;
}
// Traverse the path upwards and find all EachBlocks who are (indirectly) contributing to bind:group,
// i.e. one of their declarations is referenced in the binding. This allows group bindings to work
// correctly when referencing a variable declared in an EachBlock by using the index of the each block
// entries as keys.
const each _blocks = [ ] ;
const [ keypath , expression _ids ] = extract _all _identifiers _from _expression ( node . expression ) ;
let ids = expression _ids ;
let i = context . path . length ;
while ( i -- ) {
const parent = context . path [ i ] ;
if ( parent . type === 'EachBlock' ) {
const references = ids . filter ( ( id ) => parent . metadata . declarations . has ( id . name ) ) ;
if ( references . length > 0 ) {
parent . metadata . contains _group _binding = true ;
each _blocks . push ( parent ) ;
ids = ids . filter ( ( id ) => ! references . includes ( id ) ) ;
ids . push ( ... extract _all _identifiers _from _expression ( parent . expression ) [ 1 ] ) ;
}
}
}
// The identifiers that make up the binding expression form they key for the binding group.
// If the same identifiers in the same order are used in another bind:group, they will be in the same group.
// (there's an edge case where `bind:group={a[i]}` will be in a different group than `bind:group={a[j]}` even when i == j,
// but this is a limitation of the current static analysis we do; it also never worked in Svelte 4)
const bindings = expression _ids . map ( ( id ) => context . state . scope . get ( id . name ) ) ;
let group _name ;
outer : for ( const [ [ key , b ] , group ] of context . state . analysis . binding _groups ) {
if ( b . length !== bindings . length || key !== keypath ) continue ;
for ( let i = 0 ; i < bindings . length ; i ++ ) {
if ( bindings [ i ] !== b [ i ] ) continue outer ;
}
group _name = group ;
}
if ( ! group _name ) {
group _name = context . state . scope . root . unique ( 'binding_group' ) ;
context . state . analysis . binding _groups . set ( [ keypath , bindings ] , group _name ) ;
}
node . metadata = {
binding _group _name : group _name ,
parent _each _blocks : each _blocks
} ;
}
if ( binding ? . kind === 'each' && binding . metadata ? . inside _rest ) {
w . bind _invalid _each _rest ( binding . node , binding . node . name ) ;
}
const parent = context . path . at ( - 1 ) ;
if (
@ -218,5 +122,123 @@ export function BindDirective(node, context) {
}
}
// When dealing with bind getters/setters skip the specific binding validation
// Group bindings aren't supported for getter/setters so we don't need to handle
// the metadata
if ( node . expression . type === 'SequenceExpression' ) {
if ( node . name === 'group' ) {
e . bind _group _invalid _expression ( node ) ;
}
let i = /** @type {number} */ ( node . expression . start ) ;
while ( context . state . analysis . source [ -- i ] !== '{' ) {
if ( context . state . analysis . source [ i ] === '(' ) {
e . bind _invalid _parens ( node , node . name ) ;
}
}
if ( node . expression . expressions . length !== 2 ) {
e . bind _invalid _expression ( node ) ;
}
return ;
}
validate _no _const _assignment ( node , node . expression , context . state . scope , true ) ;
const assignee = node . expression ;
const left = object ( assignee ) ;
if ( left === null ) {
e . bind _invalid _expression ( node ) ;
}
const binding = context . state . scope . get ( left . name ) ;
if ( assignee . type === 'Identifier' ) {
// reassignment
if (
node . name !== 'this' && // bind:this also works for regular variables
( ! binding ||
( binding . kind !== 'state' &&
binding . kind !== 'raw_state' &&
binding . kind !== 'prop' &&
binding . kind !== 'bindable_prop' &&
binding . kind !== 'each' &&
binding . kind !== 'store_sub' &&
! binding . updated ) ) // TODO wut?
) {
e . bind _invalid _value ( node . expression ) ;
}
if ( context . state . analysis . runes && binding ? . kind === 'each' ) {
e . each _item _invalid _assignment ( node ) ;
}
if ( binding ? . kind === 'snippet' ) {
e . snippet _parameter _assignment ( node ) ;
}
}
if ( node . name === 'group' ) {
if ( ! binding ) {
throw new Error ( 'Cannot find declaration for bind:group' ) ;
}
// Traverse the path upwards and find all EachBlocks who are (indirectly) contributing to bind:group,
// i.e. one of their declarations is referenced in the binding. This allows group bindings to work
// correctly when referencing a variable declared in an EachBlock by using the index of the each block
// entries as keys.
const each _blocks = [ ] ;
const [ keypath , expression _ids ] = extract _all _identifiers _from _expression ( node . expression ) ;
let ids = expression _ids ;
let i = context . path . length ;
while ( i -- ) {
const parent = context . path [ i ] ;
if ( parent . type === 'EachBlock' ) {
const references = ids . filter ( ( id ) => parent . metadata . declarations . has ( id . name ) ) ;
if ( references . length > 0 ) {
parent . metadata . contains _group _binding = true ;
each _blocks . push ( parent ) ;
ids = ids . filter ( ( id ) => ! references . includes ( id ) ) ;
ids . push ( ... extract _all _identifiers _from _expression ( parent . expression ) [ 1 ] ) ;
}
}
}
// The identifiers that make up the binding expression form they key for the binding group.
// If the same identifiers in the same order are used in another bind:group, they will be in the same group.
// (there's an edge case where `bind:group={a[i]}` will be in a different group than `bind:group={a[j]}` even when i == j,
// but this is a limitation of the current static analysis we do; it also never worked in Svelte 4)
const bindings = expression _ids . map ( ( id ) => context . state . scope . get ( id . name ) ) ;
let group _name ;
outer : for ( const [ [ key , b ] , group ] of context . state . analysis . binding _groups ) {
if ( b . length !== bindings . length || key !== keypath ) continue ;
for ( let i = 0 ; i < bindings . length ; i ++ ) {
if ( bindings [ i ] !== b [ i ] ) continue outer ;
}
group _name = group ;
}
if ( ! group _name ) {
group _name = context . state . scope . root . unique ( 'binding_group' ) ;
context . state . analysis . binding _groups . set ( [ keypath , bindings ] , group _name ) ;
}
node . metadata = {
binding _group _name : group _name ,
parent _each _blocks : each _blocks
} ;
}
if ( binding ? . kind === 'each' && binding . metadata ? . inside _rest ) {
w . bind _invalid _each _rest ( binding . node , binding . node . name ) ;
}
context . next ( ) ;
}