breaking: prevent unparenthesized sequence expressions in attributes (#11032)

pull/11037/head
Rich Harris 1 year ago committed by GitHub
parent 2a1d3c6e73
commit 92909834f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: prevent unparenthesized sequence expressions in attributes

@ -282,7 +282,9 @@ const attributes = {
},
'invalid-let-directive-placement': () => 'let directive at invalid position',
'invalid-style-directive-modifier': () =>
`Invalid 'style:' modifier. Valid modifiers are: 'important'`
`Invalid 'style:' modifier. Valid modifiers are: 'important'`,
'invalid-sequence-expression': () =>
`Sequence expressions are not allowed as attribute/directive values in runes mode, unless wrapped in parentheses`
};
/** @satisfies {Errors} */

@ -41,7 +41,7 @@ export function compile(source, options) {
};
}
const analysis = analyze_component(parsed, combined_options);
const analysis = analyze_component(parsed, source, combined_options);
const result = transform_component(analysis, source, combined_options);
return result;

@ -258,10 +258,11 @@ export function analyze_module(ast, options) {
/**
* @param {import('#compiler').Root} root
* @param {string} source
* @param {import('#compiler').ValidatedCompileOptions} options
* @returns {import('../types.js').ComponentAnalysis}
*/
export function analyze_component(root, options) {
export function analyze_component(root, source, options) {
const scope_root = new ScopeRoot();
const module = js(root.module, scope_root, false, null);
@ -396,7 +397,8 @@ export function analyze_component(root, options) {
})
: '',
keyframes: []
}
},
source
};
if (!options.customElement && root.options?.customElement) {

@ -50,6 +50,18 @@ function validate_component(node, context) {
}
if (attribute.type === 'Attribute') {
if (context.state.analysis.runes && is_expression_attribute(attribute)) {
const expression = attribute.value[0].expression;
if (expression.type === 'SequenceExpression') {
let i = /** @type {number} */ (expression.start);
while (--i > 0) {
const char = context.state.analysis.source[i];
if (char === '(') break; // parenthesized sequence expressions are ok
if (char === '{') error(expression, 'invalid-sequence-expression');
}
}
}
validate_attribute_name(attribute, context);
if (attribute.name === 'slot') {
@ -81,12 +93,21 @@ function validate_element(node, context) {
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute') {
const is_expression = is_expression_attribute(attribute);
if (context.state.analysis.runes && is_expression) {
const expression = attribute.value[0].expression;
if (expression.type === 'SequenceExpression') {
error(expression, 'invalid-sequence-expression');
}
}
if (regex_illegal_attribute_character.test(attribute.name)) {
error(attribute, 'invalid-attribute-name', attribute.name);
}
if (attribute.name.startsWith('on') && attribute.name.length > 2) {
if (!is_expression_attribute(attribute)) {
if (!is_expression) {
error(attribute, 'invalid-event-attribute-value');
}

@ -73,6 +73,7 @@ export interface ComponentAnalysis extends Analysis {
hash: string;
keyframes: string[];
};
source: string;
}
declare module 'estree' {

@ -0,0 +1,10 @@
import { test } from '../../test';
export default test({
error: {
code: 'invalid-sequence-expression',
message:
'Sequence expressions are not allowed as attribute/directive values in runes mode, unless wrapped in parentheses',
position: [163, 170]
}
});

@ -0,0 +1,10 @@
<script>
import Child from './Child.svelte';
let { x, y, z } = $props();
</script>
<!-- allowed -->
<Child foo={(x, y, z)} />
<!-- not allowed -->
<Child foo={x, y, z} />
Loading…
Cancel
Save