Skip to content

Commit e9567e8

Browse files
committed
breaking: prevent unparenthesized sequence expressions in attributes
1 parent ade3d1a commit e9567e8

File tree

8 files changed

+56
-5
lines changed

8 files changed

+56
-5
lines changed

.changeset/long-humans-repair.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
breaking: prevent unparenthesized sequence expressions in attributes

packages/svelte/src/compiler/errors.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@ const attributes = {
282282
},
283283
'invalid-let-directive-placement': () => 'let directive at invalid position',
284284
'invalid-style-directive-modifier': () =>
285-
`Invalid 'style:' modifier. Valid modifiers are: 'important'`
285+
`Invalid 'style:' modifier. Valid modifiers are: 'important'`,
286+
'invalid-sequence-expression': () =>
287+
`Sequence expressions are not allowed as attribute/directive values in runes mode, unless wrapped in parentheses`
286288
};
287289

288290
/** @satisfies {Errors} */

packages/svelte/src/compiler/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function compile(source, options) {
4141
};
4242
}
4343

44-
const analysis = analyze_component(parsed, combined_options);
44+
const analysis = analyze_component(parsed, source, combined_options);
4545

4646
const result = transform_component(analysis, source, combined_options);
4747
return result;

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,11 @@ export function analyze_module(ast, options) {
258258

259259
/**
260260
* @param {import('#compiler').Root} root
261+
* @param {string} source
261262
* @param {import('#compiler').ValidatedCompileOptions} options
262263
* @returns {import('../types.js').ComponentAnalysis}
263264
*/
264-
export function analyze_component(root, options) {
265+
export function analyze_component(root, source, options) {
265266
const scope_root = new ScopeRoot();
266267

267268
const module = js(root.module, scope_root, false, null);
@@ -396,7 +397,8 @@ export function analyze_component(root, options) {
396397
})
397398
: '',
398399
keyframes: []
399-
}
400+
},
401+
source
400402
};
401403

402404
if (!options.customElement && root.options?.customElement) {

packages/svelte/src/compiler/phases/2-analyze/validation.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ function validate_component(node, context) {
5050
}
5151

5252
if (attribute.type === 'Attribute') {
53+
if (context.state.analysis.runes && is_expression_attribute(attribute)) {
54+
const expression = attribute.value[0].expression;
55+
if (expression.type === 'SequenceExpression') {
56+
let i = /** @type {number} */ (expression.start);
57+
while (--i > 0) {
58+
const char = context.state.analysis.source[i];
59+
if (char === '(') break; // parenthesized sequence expressions are ok
60+
if (char === '{') error(expression, 'invalid-sequence-expression');
61+
}
62+
}
63+
}
64+
5365
validate_attribute_name(attribute, context);
5466

5567
if (attribute.name === 'slot') {
@@ -81,12 +93,21 @@ function validate_element(node, context) {
8193

8294
for (const attribute of node.attributes) {
8395
if (attribute.type === 'Attribute') {
96+
const is_expression = is_expression_attribute(attribute);
97+
98+
if (context.state.analysis.runes && is_expression) {
99+
const expression = attribute.value[0].expression;
100+
if (expression.type === 'SequenceExpression') {
101+
error(expression, 'invalid-sequence-expression');
102+
}
103+
}
104+
84105
if (regex_illegal_attribute_character.test(attribute.name)) {
85106
error(attribute, 'invalid-attribute-name', attribute.name);
86107
}
87108

88109
if (attribute.name.startsWith('on') && attribute.name.length > 2) {
89-
if (!is_expression_attribute(attribute)) {
110+
if (!is_expression) {
90111
error(attribute, 'invalid-event-attribute-value');
91112
}
92113

packages/svelte/src/compiler/phases/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export interface ComponentAnalysis extends Analysis {
7373
hash: string;
7474
keyframes: string[];
7575
};
76+
source: string;
7677
}
7778

7879
declare module 'estree' {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
error: {
5+
code: 'invalid-sequence-expression',
6+
message:
7+
'Sequence expressions are not allowed as attribute/directive values in runes mode, unless wrapped in parentheses',
8+
position: [163, 170]
9+
}
10+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
import Child from './Child.svelte';
3+
let { x, y, z } = $props();
4+
</script>
5+
6+
<!-- allowed -->
7+
<Child foo={(x, y, z)} />
8+
9+
<!-- not allowed -->
10+
<Child foo={x, y, z} />

0 commit comments

Comments
 (0)