Skip to content

Commit 85e2790

Browse files
committed
breaking: warn/error on old syntax in runes mode
- warn on slots and event handlers in runes mode - error on `<slot>` + `{@render ...}` tag usage in same component closes #9416
1 parent 4ef6454 commit 85e2790

File tree

11 files changed

+107
-6
lines changed

11 files changed

+107
-6
lines changed

.changeset/four-pugs-listen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
breaking: warn on slots and event handlers in runes mode, error on `<slot>` + `{@render ...}` tag usage in same component

packages/svelte/src/compiler/errors.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ const special_elements = {
163163
* @param {string | null} match
164164
*/
165165
'invalid-svelte-tag': (tags, match) =>
166-
`Valid <svelte:...> tag names are ${list(tags)}${match ? ' (did you mean ' + match + '?)' : ''}`
166+
`Valid <svelte:...> tag names are ${list(tags)}${match ? ' (did you mean ' + match + '?)' : ''}`,
167+
'conflicting-slot-usage': () =>
168+
`Cannot use <slot> syntax and {@render ...} tags in the same component. Migrate towards {@render ...} tags completely.`
167169
};
168170

169171
/** @satisfies {Errors} */

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ export function analyze_component(root, source, options) {
379379
uses_rest_props: false,
380380
uses_slots: false,
381381
uses_component_bindings: false,
382+
uses_render_tags: false,
382383
custom_element: options.customElementOptions ?? options.customElement,
383384
inject_styles: options.css === 'injected' || options.customElement,
384385
accessors: options.customElement
@@ -388,7 +389,7 @@ export function analyze_component(root, source, options) {
388389
!!options.legacy?.componentApi,
389390
reactive_statements: new Map(),
390391
binding_groups: new Map(),
391-
slot_names: new Set(),
392+
slot_names: new Map(),
392393
warnings,
393394
css: {
394395
ast: root.css,
@@ -455,6 +456,10 @@ export function analyze_component(root, source, options) {
455456
}
456457
}
457458
}
459+
460+
if (analysis.uses_render_tags && (analysis.uses_slots || analysis.slot_names.size > 0)) {
461+
error(analysis.slot_names.values().next().value, 'conflicting-slot-usage');
462+
}
458463
} else {
459464
instance.scope.declare(b.id('$$props'), 'bindable_prop', 'synthetic');
460465
instance.scope.declare(b.id('$$restProps'), 'rest_prop', 'synthetic');
@@ -1087,7 +1092,7 @@ const common_visitors = {
10871092
break;
10881093
}
10891094
}
1090-
context.state.analysis.slot_names.add(name);
1095+
context.state.analysis.slot_names.set(name, node);
10911096
},
10921097
StyleDirective(node, context) {
10931098
if (node.value === true) {

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,21 @@ export const validation_runes = merge(validation, a11y_validators, {
11831183
warn(state.analysis.warnings, node, path, 'invalid-bindable-declaration');
11841184
}
11851185
},
1186+
SlotElement(node, { state, path }) {
1187+
if (!state.analysis.custom_element) {
1188+
warn(state.analysis.warnings, node, path, 'deprecated-slot-element');
1189+
}
1190+
},
1191+
OnDirective(node, { state, path }) {
1192+
const parent_type = path.at(-1)?.type;
1193+
// Don't warn on component events; these might not be under the author's control so the warning would be unactionable
1194+
if (parent_type === 'RegularElement' || parent_type === 'SvelteElement') {
1195+
warn(state.analysis.warnings, node, path, 'deprecated-event-handler', node.name);
1196+
}
1197+
},
1198+
RenderTag(_, { state }) {
1199+
state.analysis.uses_render_tags = true;
1200+
},
11861201
// TODO this is a code smell. need to refactor this stuff
11871202
ClassBody: validation_runes_js.ClassBody,
11881203
ClassDeclaration: validation_runes_js.ClassDeclaration,

packages/svelte/src/compiler/phases/types.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
Css,
44
Fragment,
55
RegularElement,
6+
SlotElement,
67
SvelteElement,
78
SvelteNode,
89
SvelteOptions
@@ -61,13 +62,14 @@ export interface ComponentAnalysis extends Analysis {
6162
/** Whether the component uses `$$slots` */
6263
uses_slots: boolean;
6364
uses_component_bindings: boolean;
65+
uses_render_tags: boolean;
6466
custom_element: boolean | SvelteOptions['customElement'];
6567
/** If `true`, should append styles through JavaScript */
6668
inject_styles: boolean;
6769
reactive_statements: Map<LabeledStatement, ReactiveStatement>;
6870
/** Identifiers that make up the `bind:group` expression -> internal group binding name */
6971
binding_groups: Map<[key: string, bindings: Array<Binding | null>], Identifier>;
70-
slot_names: Set<string>;
72+
slot_names: Map<string, SlotElement>;
7173
css: {
7274
ast: Css.StyleSheet | null;
7375
hash: string;

packages/svelte/src/compiler/warnings.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,12 @@ const legacy = {
233233
'All dependencies of the reactive declaration are declared in a module script and will not be reactive',
234234
/** @param {string} name */
235235
'unused-export-let': (name) =>
236-
`Component has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\``
236+
`Component has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\``,
237+
'deprecated-slot-element': () =>
238+
`Using <slot> to render parent content is deprecated. Use {@render ...} tags instead.`,
239+
/** @param {string} name */
240+
'deprecated-event-handler': (name) =>
241+
`Using on:${name} to listen to the ${name} event is is deprecated. Use the event attribute on${name} instead.`
237242
};
238243

239244
const block = {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
error: {
5+
code: 'conflicting-slot-usage',
6+
message:
7+
'Cannot use <slot> syntax and {@render ...} tags in the same component. Migrate towards {@render ...} tags completely.',
8+
position: [71, 84]
9+
}
10+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
let { children } = $props();
3+
</script>
4+
5+
{@render children()}
6+
<slot></slot>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let { foo } = $props();
3+
</script>
4+
5+
<!-- ok -->
6+
<button onclick={foo}>click me</button>
7+
<Button onclick={foo}>click me</Button>
8+
<Button on:click={foo}>click me</Button>
9+
10+
<!-- warn -->
11+
<slot></slot>
12+
<slot name="foo"></slot>
13+
<button on:click={foo}>click me</button>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[
2+
{
3+
"code": "deprecated-slot-element",
4+
"end": {
5+
"column": 13,
6+
"line": 11
7+
},
8+
"message": "Using <slot> to render parent content is deprecated. Use {@render ...} tags instead.",
9+
"start": {
10+
"column": 0,
11+
"line": 11
12+
}
13+
},
14+
{
15+
"code": "deprecated-slot-element",
16+
"end": {
17+
"column": 24,
18+
"line": 12
19+
},
20+
"message": "Using <slot> to render parent content is deprecated. Use {@render ...} tags instead.",
21+
"start": {
22+
"column": 0,
23+
"line": 12
24+
}
25+
},
26+
{
27+
"code": "deprecated-event-handler",
28+
"end": {
29+
"column": 22,
30+
"line": 13
31+
},
32+
"message": "Using on:click to listen to the click event is is deprecated. Use the event attribute onclick instead.",
33+
"start": {
34+
"column": 8,
35+
"line": 13
36+
}
37+
}
38+
]

packages/svelte/tests/validator/samples/static-state-reference/input.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
console.log(doubled);
99
</script>
1010

11-
<button on:click={() => count += 1}>
11+
<button onclick={() => count += 1}>
1212
clicks: {count}
1313
</button>

0 commit comments

Comments
 (0)