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-let-directive-placement': () => 'let directive at invalid position',
'invalid-style-directive-modifier': () => '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} */ /** @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); const result = transform_component(analysis, source, combined_options);
return result; return result;

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

@ -50,6 +50,18 @@ function validate_component(node, context) {
} }
if (attribute.type === 'Attribute') { 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); validate_attribute_name(attribute, context);
if (attribute.name === 'slot') { if (attribute.name === 'slot') {
@ -81,12 +93,21 @@ function validate_element(node, context) {
for (const attribute of node.attributes) { for (const attribute of node.attributes) {
if (attribute.type === 'Attribute') { 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)) { if (regex_illegal_attribute_character.test(attribute.name)) {
error(attribute, 'invalid-attribute-name', attribute.name); error(attribute, 'invalid-attribute-name', attribute.name);
} }
if (attribute.name.startsWith('on') && attribute.name.length > 2) { if (attribute.name.startsWith('on') && attribute.name.length > 2) {
if (!is_expression_attribute(attribute)) { if (!is_expression) {
error(attribute, 'invalid-event-attribute-value'); error(attribute, 'invalid-event-attribute-value');
} }

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