Skip to content

Commit dfcc5f6

Browse files
committed
use BindDirective metadata to mark a spread element instead of setting the SpreadElement as the expression
1 parent 92e9e79 commit dfcc5f6

File tree

13 files changed

+56
-76
lines changed

13 files changed

+56
-76
lines changed

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Expression, SpreadElement } from 'estree' */
1+
/** @import { Expression } from 'estree' */
22
/** @import { AST } from '#compiler' */
33
/** @import { Parser } from '../index.js' */
44
import { is_void } from '../../../../utils.js';
@@ -618,15 +618,6 @@ function read_attribute(parser) {
618618
e.directive_missing_name({ start, end: start + colon_index + 1 }, name);
619619
}
620620

621-
if (
622-
type !== 'BindDirective' &&
623-
value !== true &&
624-
'metadata' in value &&
625-
value.metadata.expression.has_spread
626-
) {
627-
e.directive_invalid_value(value.start);
628-
}
629-
630621
if (type === 'StyleDirective') {
631622
return {
632623
start,
@@ -643,7 +634,7 @@ function read_attribute(parser) {
643634

644635
const first_value = value === true ? undefined : Array.isArray(value) ? value[0] : value;
645636

646-
/** @type {Expression | SpreadElement | null} */
637+
/** @type {Expression | null} */
647638
let expression = null;
648639

649640
if (first_value) {
@@ -655,29 +646,26 @@ function read_attribute(parser) {
655646
// TODO throw a parser error in a future version here if this `[ExpressionTag]` instead of `ExpressionTag`,
656647
// which means stringified value, which isn't allowed for some directives?
657648
expression = first_value.expression;
658-
659-
if (type === 'BindDirective' && first_value.metadata.expression.has_spread) {
660-
expression = {
661-
type: 'SpreadElement',
662-
start: first_value.start,
663-
end: first_value.end,
664-
argument: expression
665-
};
666-
}
667649
}
668650
}
669651

670-
/** @type {AST.Directive} */
671-
const directive = {
652+
const directive = /** @type {AST.Directive} */ ({
672653
start,
673654
end,
674655
type,
675656
name: directive_name,
657+
modifiers: [],
676658
expression,
677659
metadata: {
678660
expression: create_expression_metadata()
679661
}
680-
};
662+
});
663+
if (first_value?.metadata.expression.has_spread) {
664+
if (directive.type !== 'BindDirective') {
665+
e.directive_invalid_value(first_value.start);
666+
}
667+
directive.metadata.spread_binding = true;
668+
}
681669

682670
// @ts-expect-error we do this separately from the declaration to avoid upsetting typescript
683671
directive.modifiers = modifiers;

packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -158,20 +158,11 @@ export function BindDirective(node, context) {
158158
return;
159159
}
160160

161-
if (node.expression.type === 'SpreadElement') {
161+
if (node.metadata.spread_binding) {
162162
if (node.name === 'group') {
163163
e.bind_group_invalid_expression(node);
164164
}
165165

166-
const argument = node.expression.argument;
167-
if (
168-
argument.type !== 'Identifier' &&
169-
argument.type !== 'MemberExpression' &&
170-
argument.type !== 'CallExpression'
171-
) {
172-
e.bind_invalid_expression(node);
173-
}
174-
175166
mark_subtree_dynamic(context.path);
176167

177168
return;
@@ -261,7 +252,8 @@ export function BindDirective(node, context) {
261252

262253
node.metadata = {
263254
binding_group_name: group_name,
264-
parent_each_blocks: each_blocks
255+
parent_each_blocks: each_blocks,
256+
spread_binding: false
265257
};
266258
}
267259

packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ export function BindDirective(node, context) {
2121

2222
let get, set;
2323

24-
// Handle SpreadElement by creating a variable declaration before visiting
25-
if (node.expression.type === 'SpreadElement') {
24+
if (node.metadata.spread_binding) {
2625
const { get: getter, set: setter } = init_spread_bindings(node.expression, context);
2726
get = getter;
2827
set = setter;

packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ function setup_select_synchronization(value_binding, context) {
414414

415415
let bound = value_binding.expression;
416416

417-
if (bound.type === 'SequenceExpression') {
417+
if (bound.type === 'SequenceExpression' || value_binding.metadata.spread_binding) {
418418
return;
419419
}
420420

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,14 @@ export function build_component(node, component_name, context) {
197197
push_prop(b.init(attribute.name, value));
198198
}
199199
} else if (attribute.type === 'BindDirective') {
200-
if (attribute.expression.type === 'SpreadElement') {
200+
if (attribute.metadata.spread_binding) {
201201
const { get, set } = init_spread_bindings(attribute.expression, context);
202202

203203
if (attribute.name === 'this') {
204-
bind_this = attribute.expression;
204+
bind_this = {
205+
type: 'SpreadElement',
206+
argument: attribute.expression
207+
};
205208
} else {
206209
push_prop(b.get(attribute.name, [b.return(b.call(get))]), true);
207210
push_prop(b.set(attribute.name, [b.stmt(b.call(set, b.id('$$value')))]), true);

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export function parse_directive_name(name) {
212212
export function build_bind_this(expression, value, context) {
213213
const { state, visit } = context;
214214
if (expression.type === 'SpreadElement') {
215-
const { get, set } = init_spread_bindings(expression, context);
215+
const { get, set } = init_spread_bindings(expression.argument, context);
216216
return b.call('$.bind_this', value, set, get);
217217
}
218218

@@ -297,10 +297,7 @@ export function build_bind_this(expression, value, context) {
297297
* @param {MemberExpression} expression
298298
*/
299299
export function validate_binding(state, binding, expression) {
300-
if (
301-
binding.expression.type === 'SequenceExpression' ||
302-
binding.expression.type === 'SpreadElement'
303-
) {
300+
if (binding.expression.type === 'SequenceExpression' || binding.metadata.spread_binding) {
304301
return;
305302
}
306303
// If we are referencing a $store.foo then we don't need to add validation

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export function build_inline_component(node, expression, context) {
9494
const value = build_attribute_value(attribute.value, context, false, true);
9595
push_prop(b.prop('init', b.key(attribute.name), value));
9696
} else if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
97-
if (attribute.expression.type === 'SpreadElement') {
97+
if (attribute.metadata.spread_binding) {
9898
const { get, set } = init_spread_bindings(attribute.expression, context);
9999

100100
push_prop(b.get(attribute.name, [b.return(b.call(get))]));

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,7 @@ export function build_element_attributes(node, context) {
119119

120120
let expression = /** @type {Expression} */ (context.visit(attribute.expression));
121121

122-
// Handle SpreadElement for bind directives
123-
if (attribute.expression.type === 'SpreadElement') {
122+
if (attribute.metadata.spread_binding) {
124123
const { get } = init_spread_bindings(attribute.expression, context);
125124
expression = b.call(get);
126125
} else if (expression.type === 'SequenceExpression') {
@@ -134,7 +133,7 @@ export function build_element_attributes(node, context) {
134133
} else if (
135134
attribute.name === 'group' &&
136135
attribute.expression.type !== 'SequenceExpression' &&
137-
attribute.expression.type !== 'SpreadElement'
136+
!attribute.metadata.spread_binding
138137
) {
139138
const value_attribute = /** @type {AST.Attribute | undefined} */ (
140139
node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'value')

packages/svelte/src/compiler/phases/3-transform/shared/spread_bindings.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
/** @import { Expression, SpreadElement } from 'estree' */
1+
/** @import { Expression } from 'estree' */
22
/** @import { ComponentContext as ClientContext } from '../client/types.js' */
33
/** @import { ComponentContext as ServerContext } from '../server/types.js' */
44
import * as b from '#compiler/builders';
55
import { dev, source } from '../../../state.js';
66

77
/**
88
* Initializes spread bindings for a SpreadElement in a bind directive.
9-
* @param {SpreadElement} spread_expression
9+
* @param {Expression} spread_expression
1010
* @param {ClientContext | ServerContext} context
1111
* @returns {{ get: Expression, set: Expression }}
1212
*/
1313
export function init_spread_bindings(spread_expression, { state, visit }) {
14-
const visited_expression = /** @type {Expression} */ (visit(spread_expression.argument));
14+
const expression = /** @type {Expression} */ (visit(spread_expression));
1515
const expression_text = dev
16-
? b.literal(source.slice(spread_expression.argument.start, spread_expression.argument.end))
16+
? b.literal(source.slice(spread_expression.start, spread_expression.end))
1717
: undefined;
1818

1919
const id = state.scope.generate('$$spread_binding');
@@ -22,7 +22,7 @@ export function init_spread_bindings(spread_expression, { state, visit }) {
2222
state.init.push(
2323
b.const(
2424
b.array_pattern([get, set]),
25-
b.call('$.validate_spread_bindings', visited_expression, expression_text)
25+
b.call('$.validate_spread_bindings', expression, expression_text)
2626
)
2727
);
2828

packages/svelte/src/compiler/types/legacy-nodes.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import type {
77
MemberExpression,
88
ObjectExpression,
99
Pattern,
10-
SequenceExpression
10+
SequenceExpression,
11+
SpreadElement
1112
} from 'estree';
1213

1314
interface BaseNode {
@@ -50,7 +51,7 @@ export interface LegacyBinding extends BaseNode {
5051
/** The 'x' in `bind:x` */
5152
name: string;
5253
/** The y in `bind:x={y}` */
53-
expression: Identifier | MemberExpression | SequenceExpression;
54+
expression: Identifier | MemberExpression | SequenceExpression | SpreadElement;
5455
}
5556

5657
export interface LegacyBody extends BaseElement {

0 commit comments

Comments
 (0)