Skip to content

Commit 5fc47f9

Browse files
committed
warn on quoted single-expression-attributes
1 parent 9813526 commit 5fc47f9

File tree

7 files changed

+117
-8
lines changed

7 files changed

+117
-8
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
> '%wrong%' is not a valid HTML attribute. Did you mean '%right%'?
1616
17+
## attribute_quoted
18+
19+
> Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.
20+
1721
## bind_invalid_each_rest
1822

1923
> The rest operator (...) will create a new object and binding '%name%' with the original object will not work

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ function read_attribute(parser) {
541541
}
542542
};
543543

544-
return create_attribute(name, start, parser.index, [expression]);
544+
return create_attribute(name, start, parser.index, expression);
545545
}
546546
}
547547

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,23 @@ import { Scope, get_rune } from '../scope.js';
3434
import { merge } from '../visitors.js';
3535
import { a11y_validators } from './a11y.js';
3636

37-
/** @param {import('#compiler').Attribute} attribute */
38-
function validate_attribute(attribute) {
37+
/**
38+
* @param {import('#compiler').Attribute} attribute
39+
* @param {import('#compiler').ElementLike} parent
40+
*/
41+
function validate_attribute(attribute, parent) {
42+
if (
43+
Array.isArray(attribute.value) &&
44+
attribute.value.length === 1 &&
45+
attribute.value[0].type === 'ExpressionTag' &&
46+
(parent.type === 'Component' ||
47+
parent.type === 'SvelteComponent' ||
48+
parent.type === 'SvelteSelf' ||
49+
(parent.type === 'RegularElement' && is_custom_element_node(parent)))
50+
) {
51+
w.attribute_quoted(attribute);
52+
}
53+
3954
if (attribute.value === true || !Array.isArray(attribute.value) || attribute.value.length === 1) {
4055
return;
4156
}
@@ -72,7 +87,7 @@ function validate_component(node, context) {
7287

7388
if (attribute.type === 'Attribute') {
7489
if (context.state.analysis.runes) {
75-
validate_attribute(attribute);
90+
validate_attribute(attribute, node);
7691

7792
if (is_expression_attribute(attribute)) {
7893
const expression = get_attribute_expression(attribute);
@@ -125,7 +140,7 @@ function validate_element(node, context) {
125140
const is_expression = is_expression_attribute(attribute);
126141

127142
if (context.state.analysis.runes) {
128-
validate_attribute(attribute);
143+
validate_attribute(attribute, node);
129144

130145
if (is_expression) {
131146
const expression = get_attribute_expression(attribute);

packages/svelte/src/compiler/warnings.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export const codes = [
108108
"attribute_global_event_reference",
109109
"attribute_illegal_colon",
110110
"attribute_invalid_property_name",
111+
"attribute_quoted",
111112
"bind_invalid_each_rest",
112113
"block_empty",
113114
"component_name_lowercase",
@@ -686,6 +687,14 @@ export function attribute_invalid_property_name(node, wrong, right) {
686687
w(node, "attribute_invalid_property_name", `'${wrong}' is not a valid HTML attribute. Did you mean '${right}'?`);
687688
}
688689

690+
/**
691+
* Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.
692+
* @param {null | NodeLike} node
693+
*/
694+
export function attribute_quoted(node) {
695+
w(node, "attribute_quoted", "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.");
696+
}
697+
689698
/**
690699
* The rest operator (...) will create a new object and binding '%name%' with the original object will not work
691700
* @param {null | NodeLike} node
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<svelte:options runes />
2+
3+
<!-- don't warn on these -->
4+
5+
<!-- prettier-ignore -->
6+
<p class="{foo}"></p>
7+
<!-- prettier-ignore -->
8+
<svelte:element this={foo} class="{foo}"></svelte:element>
9+
10+
<!-- warn on these -->
11+
12+
<!-- prettier-ignore -->
13+
<Component class="{foo}" />
14+
<!-- prettier-ignore -->
15+
<svelte:component this={foo} class="{foo}" />
16+
<!-- prettier-ignore -->
17+
{#if foo}
18+
<svelte:self class="{foo}" />
19+
{/if}
20+
<!-- prettier-ignore -->
21+
<custom-element class="{foo}"></custom-element>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[
2+
{
3+
"code": "attribute_quoted",
4+
"message": "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.",
5+
"start": {
6+
"column": 11,
7+
"line": 13
8+
},
9+
"end": {
10+
"column": 24,
11+
"line": 13
12+
}
13+
},
14+
{
15+
"code": "attribute_quoted",
16+
"message": "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.",
17+
"start": {
18+
"column": 29,
19+
"line": 15
20+
},
21+
"end": {
22+
"column": 42,
23+
"line": 15
24+
}
25+
},
26+
{
27+
"code": "attribute_quoted",
28+
"message": "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.",
29+
"start": {
30+
"column": 14,
31+
"line": 18
32+
},
33+
"end": {
34+
"column": 27,
35+
"line": 18
36+
}
37+
},
38+
{
39+
"code": "attribute_quoted",
40+
"message": "Quoted attributes on components and custom elements will be stringified in a future version of Svelte. If this isn't what you want, remove the quotes.",
41+
"start": {
42+
"column": 16,
43+
"line": 21
44+
},
45+
"end": {
46+
"column": 29,
47+
"line": 21
48+
}
49+
}
50+
]

packages/svelte/types/index.d.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,16 @@ declare module 'svelte/compiler' {
11531153
outro: boolean;
11541154
}
11551155

1156+
/** A `style:` directive */
1157+
interface LegacyStyleDirective extends BaseNode_1 {
1158+
type: 'StyleDirective';
1159+
/** The 'x' in `style:x` */
1160+
name: string;
1161+
/** The 'y' in `style:x={y}` */
1162+
value: true | Array<ExpressionTag | Text>;
1163+
modifiers: Array<'important'>;
1164+
}
1165+
11561166
interface LegacyWindow extends BaseElement_1 {
11571167
type: 'Window';
11581168
}
@@ -1171,7 +1181,7 @@ declare module 'svelte/compiler' {
11711181
| LegacyClass
11721182
| LegacyLet
11731183
| LegacyEventHandler
1174-
| StyleDirective
1184+
| LegacyStyleDirective
11751185
| LegacyTransition
11761186
| LegacyAction;
11771187

@@ -1634,7 +1644,7 @@ declare module 'svelte/compiler' {
16341644
/** The 'x' in `style:x` */
16351645
name: string;
16361646
/** The 'y' in `style:x={y}` */
1637-
value: true | Array<ExpressionTag | Text>;
1647+
value: true | ExpressionTag | Array<ExpressionTag | Text>;
16381648
modifiers: Array<'important'>;
16391649
metadata: {
16401650
dynamic: boolean;
@@ -1855,7 +1865,7 @@ declare module 'svelte/compiler' {
18551865
interface Attribute extends BaseNode {
18561866
type: 'Attribute';
18571867
name: string;
1858-
value: true | Array<Text | ExpressionTag>;
1868+
value: true | ExpressionTag | Array<Text | ExpressionTag>;
18591869
metadata: {
18601870
dynamic: boolean;
18611871
/** May be set if this is an event attribute */

0 commit comments

Comments
 (0)