Skip to content

Commit d15fd95

Browse files
authored
chore: better invalid attribute message (#11754)
* simplify code * replace error code and message * update message, update tests
1 parent 24151c4 commit d15fd95

File tree

7 files changed

+55
-63
lines changed

7 files changed

+55
-63
lines changed

packages/svelte/messages/compile-errors/template.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@
3030

3131
> Event attribute must be a JavaScript expression, not a string
3232
33-
## attribute_invalid_expression
34-
35-
> Invalid attribute expression
36-
3733
## attribute_invalid_multiple
3834

3935
> 'multiple' attribute must be static if select uses two-way binding
@@ -50,6 +46,10 @@
5046

5147
> 'type' attribute must be a static text value if input uses two-way binding
5248
49+
## attribute_unquoted_sequence
50+
51+
> Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression
52+
5353
## bind_invalid_expression
5454

5555
> Can only bind to an Identifier or MemberExpression

packages/svelte/src/compiler/errors.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -600,15 +600,6 @@ export function attribute_invalid_event_handler(node) {
600600
e(node, "attribute_invalid_event_handler", "Event attribute must be a JavaScript expression, not a string");
601601
}
602602

603-
/**
604-
* Invalid attribute expression
605-
* @param {null | number | NodeLike} node
606-
* @returns {never}
607-
*/
608-
export function attribute_invalid_expression(node) {
609-
e(node, "attribute_invalid_expression", "Invalid attribute expression");
610-
}
611-
612603
/**
613604
* 'multiple' attribute must be static if select uses two-way binding
614605
* @param {null | number | NodeLike} node
@@ -646,6 +637,15 @@ export function attribute_invalid_type(node) {
646637
e(node, "attribute_invalid_type", "'type' attribute must be a static text value if input uses two-way binding");
647638
}
648639

640+
/**
641+
* Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression
642+
* @param {null | number | NodeLike} node
643+
* @returns {never}
644+
*/
645+
export function attribute_unquoted_sequence(node) {
646+
e(node, "attribute_unquoted_sequence", "Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression");
647+
}
648+
649649
/**
650650
* Can only bind to an Identifier or MemberExpression
651651
* @param {null | number | NodeLike} node

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

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
extract_identifiers,
1010
get_parent,
1111
is_expression_attribute,
12-
is_quoted_attribute,
1312
is_text_attribute,
1413
object,
1514
unwrap_optional
@@ -34,6 +33,17 @@ import { Scope, get_rune } from '../scope.js';
3433
import { merge } from '../visitors.js';
3534
import { a11y_validators } from './a11y.js';
3635

36+
/** @param {import('#compiler').Attribute} attribute */
37+
function validate_attribute(attribute) {
38+
if (attribute.value === true || attribute.value.length === 1) return;
39+
40+
const is_quoted = attribute.value.at(-1)?.end !== attribute.end;
41+
42+
if (!is_quoted) {
43+
e.attribute_unquoted_sequence(attribute);
44+
}
45+
}
46+
3747
/**
3848
* @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node
3949
* @param {import('zimmerframe').Context<import('#compiler').SvelteNode, import('./types.js').AnalysisState>} context
@@ -58,23 +68,18 @@ function validate_component(node, context) {
5868
}
5969

6070
if (attribute.type === 'Attribute') {
61-
if (
62-
context.state.analysis.runes &&
63-
!is_quoted_attribute(attribute) &&
64-
Array.isArray(attribute.value) &&
65-
attribute.value.length > 1
66-
) {
67-
e.attribute_invalid_expression(attribute);
68-
}
69-
70-
if (context.state.analysis.runes && is_expression_attribute(attribute)) {
71-
const expression = attribute.value[0].expression;
72-
if (expression.type === 'SequenceExpression') {
73-
let i = /** @type {number} */ (expression.start);
74-
while (--i > 0) {
75-
const char = context.state.analysis.source[i];
76-
if (char === '(') break; // parenthesized sequence expressions are ok
77-
if (char === '{') e.attribute_invalid_sequence_expression(expression);
71+
if (context.state.analysis.runes) {
72+
validate_attribute(attribute);
73+
74+
if (is_expression_attribute(attribute)) {
75+
const expression = attribute.value[0].expression;
76+
if (expression.type === 'SequenceExpression') {
77+
let i = /** @type {number} */ (expression.start);
78+
while (--i > 0) {
79+
const char = context.state.analysis.source[i];
80+
if (char === '(') break; // parenthesized sequence expressions are ok
81+
if (char === '{') e.attribute_invalid_sequence_expression(expression);
82+
}
7883
}
7984
}
8085
}
@@ -116,23 +121,18 @@ function validate_element(node, context) {
116121
if (attribute.type === 'Attribute') {
117122
const is_expression = is_expression_attribute(attribute);
118123

119-
if (
120-
context.state.analysis.runes &&
121-
!is_quoted_attribute(attribute) &&
122-
Array.isArray(attribute.value) &&
123-
attribute.value.length > 1
124-
) {
125-
e.attribute_invalid_expression(attribute);
126-
}
127-
128-
if (context.state.analysis.runes && is_expression) {
129-
const expression = attribute.value[0].expression;
130-
if (expression.type === 'SequenceExpression') {
131-
let i = /** @type {number} */ (expression.start);
132-
while (--i > 0) {
133-
const char = context.state.analysis.source[i];
134-
if (char === '(') break; // parenthesized sequence expressions are ok
135-
if (char === '{') e.attribute_invalid_sequence_expression(expression);
124+
if (context.state.analysis.runes) {
125+
validate_attribute(attribute);
126+
127+
if (is_expression) {
128+
const expression = attribute.value[0].expression;
129+
if (expression.type === 'SequenceExpression') {
130+
let i = /** @type {number} */ (expression.start);
131+
while (--i > 0) {
132+
const char = context.state.analysis.source[i];
133+
if (char === '(') break; // parenthesized sequence expressions are ok
134+
if (char === '{') e.attribute_invalid_sequence_expression(expression);
135+
}
136136
}
137137
}
138138
}

packages/svelte/src/compiler/utils/ast.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,6 @@ export function is_expression_attribute(attribute) {
4444
);
4545
}
4646

47-
/**
48-
* Returns true if the attribute is quoted.
49-
* @param {import('#compiler').Attribute} attribute
50-
* @returns {attribute is import('#compiler').Attribute & { value: [import('#compiler').ExpressionTag] }}
51-
*/
52-
export function is_quoted_attribute(attribute) {
53-
if (attribute.value === true) return false;
54-
return attribute.value.at(-1)?.end !== attribute.end;
55-
}
56-
5747
/**
5848
* Returns true if the attribute starts with `on` and contains a single expression node.
5949
* @param {import('#compiler').Attribute} attribute

packages/svelte/tests/compiler-errors/samples/unbalanced-curly-component/_config.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'attribute_invalid_expression',
6-
message: 'Invalid attribute expression',
5+
code: 'attribute_unquoted_sequence',
6+
message:
7+
'Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression',
78
position: [101, 116]
89
}
910
});

packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/_config.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'attribute_invalid_expression',
6-
message: 'Invalid attribute expression',
5+
code: 'attribute_unquoted_sequence',
6+
message:
7+
'Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression',
78
position: [34, 71]
89
}
910
});

packages/svelte/tests/compiler-errors/samples/unbalanced-curly-element/main.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
onclick={() => console.log('hello')}}
44
>
55
click
6-
</button>
6+
</button>

0 commit comments

Comments
 (0)