diff --git a/packages/svelte/messages/compile-errors/template.md b/packages/svelte/messages/compile-errors/template.md index bacd9c50d8..b35d83cf8f 100644 --- a/packages/svelte/messages/compile-errors/template.md +++ b/packages/svelte/messages/compile-errors/template.md @@ -30,10 +30,6 @@ > Event attribute must be a JavaScript expression, not a string -## attribute_invalid_expression - -> Invalid attribute expression - ## attribute_invalid_multiple > 'multiple' attribute must be static if select uses two-way binding @@ -50,6 +46,10 @@ > 'type' attribute must be a static text value if input uses two-way binding +## attribute_unquoted_sequence + +> Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression + ## bind_invalid_expression > Can only bind to an Identifier or MemberExpression diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index a18e145332..df75245a68 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -600,15 +600,6 @@ export function attribute_invalid_event_handler(node) { e(node, "attribute_invalid_event_handler", "Event attribute must be a JavaScript expression, not a string"); } -/** - * Invalid attribute expression - * @param {null | number | NodeLike} node - * @returns {never} - */ -export function attribute_invalid_expression(node) { - e(node, "attribute_invalid_expression", "Invalid attribute expression"); -} - /** * 'multiple' attribute must be static if select uses two-way binding * @param {null | number | NodeLike} node @@ -646,6 +637,15 @@ export function attribute_invalid_type(node) { e(node, "attribute_invalid_type", "'type' attribute must be a static text value if input uses two-way binding"); } +/** + * Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression + * @param {null | number | NodeLike} node + * @returns {never} + */ +export function attribute_unquoted_sequence(node) { + e(node, "attribute_unquoted_sequence", "Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression"); +} + /** * Can only bind to an Identifier or MemberExpression * @param {null | number | NodeLike} node diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index d05fbcb4fa..7bf9773800 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -9,7 +9,6 @@ import { extract_identifiers, get_parent, is_expression_attribute, - is_quoted_attribute, is_text_attribute, object, unwrap_optional @@ -34,6 +33,17 @@ import { Scope, get_rune } from '../scope.js'; import { merge } from '../visitors.js'; import { a11y_validators } from './a11y.js'; +/** @param {import('#compiler').Attribute} attribute */ +function validate_attribute(attribute) { + if (attribute.value === true || attribute.value.length === 1) return; + + const is_quoted = attribute.value.at(-1)?.end !== attribute.end; + + if (!is_quoted) { + e.attribute_unquoted_sequence(attribute); + } +} + /** * @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node * @param {import('zimmerframe').Context} context @@ -58,23 +68,18 @@ function validate_component(node, context) { } if (attribute.type === 'Attribute') { - if ( - context.state.analysis.runes && - !is_quoted_attribute(attribute) && - Array.isArray(attribute.value) && - attribute.value.length > 1 - ) { - e.attribute_invalid_expression(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 === '{') e.attribute_invalid_sequence_expression(expression); + if (context.state.analysis.runes) { + validate_attribute(attribute); + + if (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 === '{') e.attribute_invalid_sequence_expression(expression); + } } } } @@ -116,23 +121,18 @@ function validate_element(node, context) { if (attribute.type === 'Attribute') { const is_expression = is_expression_attribute(attribute); - if ( - context.state.analysis.runes && - !is_quoted_attribute(attribute) && - Array.isArray(attribute.value) && - attribute.value.length > 1 - ) { - e.attribute_invalid_expression(attribute); - } - - if (context.state.analysis.runes && is_expression) { - 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 === '{') e.attribute_invalid_sequence_expression(expression); + if (context.state.analysis.runes) { + validate_attribute(attribute); + + if (is_expression) { + 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 === '{') e.attribute_invalid_sequence_expression(expression); + } } } } diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js index 747956ab86..7a5062382f 100644 --- a/packages/svelte/src/compiler/utils/ast.js +++ b/packages/svelte/src/compiler/utils/ast.js @@ -44,16 +44,6 @@ export function is_expression_attribute(attribute) { ); } -/** - * Returns true if the attribute is quoted. - * @param {import('#compiler').Attribute} attribute - * @returns {attribute is import('#compiler').Attribute & { value: [import('#compiler').ExpressionTag] }} - */ -export function is_quoted_attribute(attribute) { - if (attribute.value === true) return false; - return attribute.value.at(-1)?.end !== attribute.end; -} - /** * Returns true if the attribute starts with `on` and contains a single expression node. * @param {import('#compiler').Attribute} attribute diff --git a/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-component/_config.js b/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-component/_config.js index 77d662ebc7..cd3a73a1c7 100644 --- a/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-component/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-component/_config.js @@ -2,8 +2,9 @@ import { test } from '../../test'; export default test({ error: { - code: 'attribute_invalid_expression', - message: 'Invalid attribute expression', + code: 'attribute_unquoted_sequence', + message: + 'Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression', position: [101, 116] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/_config.js b/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/_config.js index db927e8399..4ce7a3d285 100644 --- a/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/_config.js @@ -2,8 +2,9 @@ import { test } from '../../test'; export default test({ error: { - code: 'attribute_invalid_expression', - message: 'Invalid attribute expression', + code: 'attribute_unquoted_sequence', + message: + 'Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression', position: [34, 71] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/main.svelte b/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/main.svelte index 0d9bbce366..0ea66fecb0 100644 --- a/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/main.svelte +++ b/packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/main.svelte @@ -3,4 +3,4 @@ onclick={() => console.log('hello')}} > click - \ No newline at end of file +