pull/16607/head
Rich Harris 1 week ago
parent c20a12fff0
commit 3a462ce35c

@ -608,8 +608,33 @@ function read_attribute(parser) {
}
];
end = parser.index;
} else if (type === 'BindDirective') {
// allow quote marks for legacy reasons. TODO 6.0 disallow
const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null;
parser.eat('{', true);
const spread = parser.eat('...');
const expression = spread ? b.spread(read_expression(parser)) : read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
if (quote_mark) parser.eat(quote_mark, true);
end = parser.index;
return {
start,
end,
type,
name: tag.name.slice(5),
modifiers: [],
name_loc: tag.loc,
// @ts-ignore
expression,
// @ts-ignore
metadata: {
expression: new ExpressionMetadata()
}
};
} else {
value = read_attribute_value(parser, type);
value = read_attribute_value(parser);
end = parser.index;
}
} else if (parser.match_regex(regex_starts_with_quote_characters)) {
@ -667,13 +692,6 @@ function read_attribute(parser) {
}
});
if (first_value?.metadata.expression.has_spread) {
if (directive.type !== 'BindDirective') {
e.directive_invalid_value(first_value.start);
}
directive.metadata.spread_binding = true;
}
// @ts-expect-error we do this separately from the declaration to avoid upsetting typescript
directive.modifiers = modifiers;
@ -718,10 +736,9 @@ function get_directive_type(name) {
/**
* @param {Parser} parser
* @param {AST.AttributeLike['type'] | undefined} type
* @return {AST.ExpressionTag | Array<AST.ExpressionTag | AST.Text>}
*/
function read_attribute_value(parser, type) {
function read_attribute_value(parser) {
const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null;
if (quote_mark && parser.eat(quote_mark)) {
return [
@ -745,8 +762,7 @@ function read_attribute_value(parser, type) {
if (quote_mark) return parser.match(quote_mark);
return !!parser.match_regex(regex_invalid_unquoted_attribute_value);
},
'in attribute value',
type === 'BindDirective'
'in attribute value'
);
} catch (/** @type {any} */ error) {
if (error.code === 'js_parse_error') {
@ -779,10 +795,9 @@ function read_attribute_value(parser, type) {
* @param {Parser} parser
* @param {() => boolean} done
* @param {string} location
* @param {boolean} allow_spread
* @returns {any[]}
*/
function read_sequence(parser, done, location, allow_spread = false) {
function read_sequence(parser, done, location) {
/** @type {AST.Text} */
let current_chunk = {
start: parser.index,
@ -827,8 +842,6 @@ function read_sequence(parser, done, location, allow_spread = false) {
parser.allow_whitespace();
const has_spread = allow_spread && parser.eat('...');
const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
@ -844,8 +857,6 @@ function read_sequence(parser, done, location, allow_spread = false) {
}
};
chunk.metadata.expression.has_spread = has_spread;
chunks.push(chunk);
current_chunk = {

@ -738,7 +738,7 @@ export function analyze_component(root, source, options) {
type === 'FunctionExpression' ||
type === 'ArrowFunctionExpression' ||
(type === 'BindDirective' &&
/** @type {AST.BindDirective} */ (path[i]).metadata.spread_binding)
/** @type {AST.BindDirective} */ (path[i]).expression.type === 'SpreadElement')
) {
continue inner;
}

@ -178,7 +178,7 @@ export function BindDirective(node, context) {
return;
}
if (node.metadata.spread_binding) {
if (node.expression.type === 'SpreadElement') {
if (node.name === 'group') {
e.bind_group_invalid_expression(node);
}

@ -1,4 +1,4 @@
/** @import { CallExpression, Expression, Pattern } from 'estree' */
/** @import { CallExpression, Expression, Pattern, SpreadElement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
import { dev, is_ignored } from '../../../../state.js';
@ -14,15 +14,19 @@ import { init_spread_bindings } from '../../shared/spread_bindings.js';
* @param {ComponentContext} context
*/
export function BindDirective(node, context) {
const expression = /** @type {Expression} */ (context.visit(node.expression));
const property = binding_properties[node.name];
const expression = /** @type {Expression} */ (
context.visit(
node.expression.type === 'SpreadElement' ? node.expression.argument : node.expression
)
);
const property = binding_properties[node.name];
const parent = /** @type {AST.SvelteNode} */ (context.path.at(-1));
let get, set;
if (node.metadata.spread_binding) {
[get, set] = init_spread_bindings(node.expression, context);
if (node.expression.type === 'SpreadElement') {
[get, set] = init_spread_bindings(expression, context);
} else if (expression.type === 'SequenceExpression') {
[get, set] = expression.expressions;
} else {

@ -515,7 +515,7 @@ function setup_select_synchronization(value_binding, context) {
let bound = value_binding.expression;
if (bound.type === 'SequenceExpression' || value_binding.metadata.spread_binding) {
if (bound.type === 'SequenceExpression' || bound.type === 'SpreadElement') {
return;
}

@ -212,7 +212,7 @@ export function build_component(node, component_name, loc, context) {
// bind:x={() => x.y, y => x.y = y} and bind:x={...[() => x.y, y => x.y = y]}
// will be handled by the assignment expression binding validation
attribute.expression.type !== 'SequenceExpression' &&
!attribute.metadata.spread_binding
attribute.expression.type !== 'SpreadElement'
) {
const left = object(attribute.expression);
const binding = left && context.state.scope.get(left.name);
@ -232,13 +232,13 @@ export function build_component(node, component_name, loc, context) {
}
}
if (attribute.metadata.spread_binding) {
const [get, set] = init_spread_bindings(attribute.expression, context);
if (attribute.expression.type === 'SpreadElement') {
const [get, set] = init_spread_bindings(attribute.expression.argument, context);
if (attribute.name === 'this') {
bind_this = {
type: 'SpreadElement',
argument: attribute.expression
argument: attribute.expression.argument
};
} else {
push_prop(b.get(attribute.name, [b.return(b.call(get))]), true);

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

@ -111,8 +111,8 @@ export function build_inline_component(node, expression, context) {
// Bindings are a bit special: we don't want to add them to (async) deriveds but we need to check if they have blockers
optimiser.check_blockers(attribute.metadata.expression);
if (attribute.metadata.spread_binding) {
const [get, set] = init_spread_bindings(attribute.expression, context);
if (attribute.expression.type === 'SpreadElement') {
const [get, set] = init_spread_bindings(attribute.expression.argument, context);
push_prop(b.get(attribute.name, [b.return(b.call(get))]));
push_prop(b.set(attribute.name, [b.stmt(b.call(set, b.id('$$value')))]));

@ -116,10 +116,16 @@ export function build_element_attributes(node, context, transform) {
const binding = binding_properties[attribute.name];
if (binding?.omit_in_ssr) continue;
let expression = /** @type {Expression} */ (context.visit(attribute.expression));
let expression = /** @type {Expression} */ (
context.visit(
attribute.expression.type === 'SpreadElement'
? attribute.expression.argument
: attribute.expression
)
);
if (attribute.metadata.spread_binding) {
const [get] = init_spread_bindings(attribute.expression, context);
if (attribute.expression.type === 'SpreadElement') {
const [get] = init_spread_bindings(expression, context);
expression = b.call(get);
} else if (expression.type === 'SequenceExpression') {
expression = b.call(expression.expressions[0]);
@ -134,7 +140,7 @@ export function build_element_attributes(node, context, transform) {
} else if (
attribute.name === 'group' &&
attribute.expression.type !== 'SequenceExpression' &&
!attribute.metadata.spread_binding
attribute.expression.type !== 'SpreadElement'
) {
const value_attribute = /** @type {AST.Attribute | undefined} */ (
node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'value')

@ -77,9 +77,6 @@ export class ExpressionMetadata {
/** True if the expression contains `await` */
has_await = false;
/** True if the expression includes a spread element */
has_spread = false;
/** True if the expression includes a member expression */
has_member_expression = false;

@ -14,7 +14,8 @@ import type {
ChainExpression,
SimpleCallExpression,
SequenceExpression,
SourceLocation
SourceLocation,
SpreadElement
} from 'estree';
import type { Scope } from '../phases/scope';
import type { _CSS } from './css';
@ -207,13 +208,12 @@ export namespace AST {
/** The 'x' in `bind:x` */
name: string;
/** The y in `bind:x={y}` */
expression: Identifier | MemberExpression | SequenceExpression;
expression: Identifier | MemberExpression | SequenceExpression | SpreadElement;
/** @internal */
metadata: {
binding?: Binding | null;
binding_group_name: Identifier;
parent_each_blocks: EachBlock[];
spread_binding: boolean;
expression: ExpressionMetadata;
};
}

Loading…
Cancel
Save