Merge branch 'main' into pr/13130

pull/13130/head
Rich Harris 2 years ago
commit cbec56fe9d

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure reactive graph is fully traversed in the marking phase for non-runes mode

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: visit expression for `svelte:component` references

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure signal graph is consistent before triggering $inspect signals

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure reactivity system remains consistent with removals

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: better types for the `autocomplete` attribute

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: error on duplicate style and class directive

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: provide guidance in browser console when logging $state objects

@ -0,0 +1,5 @@
---
'svelte': patch
---
perf: inline module variables into template

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: render undefined html as the empty string

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: error on incorrect attributes for svelte:body

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure snapshot logs don't affect dependency graph

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent div/0 when generating transition keyframes

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: widen ownership upon property access if necessary

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: use WAAPI to control timing of JS-based animations

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: don't show `state_referenced_locally` warning on types

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: allow custom element events on slot to bubble inside custom element

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure locally mutated bindable props persist with spreading props

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: error on invalid element name

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: wait until template strings are complete before sanitizing

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: better compile errors for invalid tag names/placement

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure $host rune correctly compiles in variable declarations

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: avoid flushing sync with $inspect

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: add missing `autocomplete` attribute tokens

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: remove unnecessary update assignments

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure correct parent effect is associated with render effects

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: try catch `strict_equals` to avoid error accessing `STATE_SYMBOL`

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure types are easier to follow for TypeScript

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: separate `template_effect` for dynamic class/style directive with dynamic attributes

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: insert comment before text in an each block, to prevent glued nodes

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: error at compile time on unsupported TypeScript language features

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure $inspect effects are fine-grain

@ -18,6 +18,7 @@
"angry-books-jam",
"angry-plums-punch",
"angry-wasps-help",
"angry-weeks-design",
"beige-cobras-smoke",
"beige-flies-wash",
"beige-gifts-appear",
@ -37,6 +38,7 @@
"blue-timers-film",
"blue-waves-sneeze",
"brave-carrots-draw",
"brave-doors-compete",
"brave-gorillas-end",
"brave-gorillas-fold",
"brave-pigs-obey",
@ -70,6 +72,7 @@
"chatty-cups-drop",
"chatty-ghosts-unite",
"chatty-sloths-allow",
"chatty-snails-train",
"chatty-taxis-juggle",
"chilled-ladybugs-invite",
"chilled-pumas-invite",
@ -80,6 +83,7 @@
"chilly-pans-raise",
"chilly-rocks-hug",
"chilly-snakes-scream",
"chilly-waves-count",
"clean-cats-wave",
"clean-eels-beg",
"clean-melons-wash",
@ -113,6 +117,7 @@
"curvy-buses-laugh",
"curvy-cups-cough",
"curvy-flies-exercise",
"curvy-foxes-wink",
"curvy-papayas-pretend",
"curvy-ties-shout",
"cyan-colts-raise",
@ -126,6 +131,7 @@
"dirty-donuts-yell",
"dirty-eyes-itch",
"dirty-garlics-design",
"dirty-jars-tap",
"dirty-pens-look",
"dirty-pianos-eat",
"dirty-tips-add",
@ -146,6 +152,7 @@
"dull-worms-relax",
"early-ads-tie",
"early-months-whisper",
"early-taxis-allow",
"eight-carrots-hunt",
"eight-comics-tell",
"eight-cougars-watch",
@ -157,13 +164,17 @@
"eighty-bugs-fetch",
"eighty-days-cheat",
"eighty-days-wave",
"eighty-dragons-search",
"eighty-lizards-notice",
"eighty-mails-develop",
"eleven-avocados-walk",
"eleven-beers-yell",
"eleven-cows-judge",
"eleven-cycles-applaud",
"eleven-donuts-sit",
"eleven-hounds-pump",
"eleven-icons-sniff",
"eleven-peaches-tap",
"eleven-teachers-drive",
"empty-bags-heal",
"empty-bulldogs-exercise",
@ -194,15 +205,20 @@
"few-clouds-shop",
"few-mugs-fail",
"few-teachers-know",
"few-walls-check",
"few-zoos-own",
"fifty-actors-agree",
"fifty-masks-give",
"fifty-rice-wait",
"fifty-steaks-float",
"fifty-toys-invite",
"five-birds-check",
"five-maps-reflect",
"five-shirts-run",
"five-suns-roll",
"five-tigers-search",
"flat-feet-visit",
"flat-flies-know",
"flat-ghosts-fly",
"flat-melons-protect",
"flat-olives-live",
@ -244,8 +260,10 @@
"funny-wombats-argue",
"fuzzy-bags-camp",
"fuzzy-donuts-provide",
"fuzzy-tigers-swim",
"gentle-dolls-juggle",
"gentle-eagles-walk",
"gentle-needles-train",
"gentle-sheep-hug",
"gentle-spies-happen",
"gentle-ties-fetch",
@ -267,6 +285,7 @@
"good-rice-tap",
"good-rivers-yawn",
"good-roses-argue",
"good-zebras-turn",
"gorgeous-boxes-design",
"gorgeous-coats-jog",
"gorgeous-hats-wonder",
@ -289,6 +308,7 @@
"grumpy-jars-sparkle",
"happy-beds-scream",
"happy-dogs-jump",
"happy-dolls-joke",
"happy-lobsters-lick",
"happy-moles-live",
"happy-planets-rest",
@ -298,6 +318,7 @@
"healthy-mangos-brush",
"healthy-planes-vanish",
"healthy-zebras-accept",
"heavy-cars-ring",
"heavy-comics-move",
"heavy-doors-applaud",
"heavy-ducks-leave",
@ -318,8 +339,10 @@
"honest-pans-kick",
"honest-phones-flash",
"hot-cooks-repair",
"hot-glasses-roll",
"hot-jobs-tap",
"hot-kangaroos-invite",
"hot-peaches-clean",
"hot-rivers-punch",
"hot-sloths-clap",
"hot-tips-appear",
@ -336,10 +359,12 @@
"itchy-kings-deliver",
"itchy-lemons-punch",
"itchy-lions-wash",
"itchy-oranges-behave",
"itchy-panthers-shave",
"itchy-peaches-compare",
"itchy-pianos-marry",
"itchy-terms-guess",
"khaki-camels-punch",
"khaki-cheetahs-refuse",
"khaki-cooks-develop",
"khaki-donkeys-jump",
@ -363,6 +388,7 @@
"large-waves-join",
"late-bees-vanish",
"late-crabs-lay",
"late-geckos-draw",
"late-grapes-judge",
"late-peaches-mate",
"late-zebras-argue",
@ -418,6 +444,7 @@
"mean-jokes-exist",
"mean-numbers-cry",
"mean-parents-film",
"metal-cameras-jump",
"metal-clouds-raise",
"metal-lobsters-burn",
"metal-pans-trade",
@ -439,6 +466,7 @@
"moody-sheep-type",
"moody-toys-relax",
"nasty-carrots-develop",
"nasty-eggs-walk",
"nasty-glasses-begin",
"nasty-lions-double",
"nasty-mayflies-smoke",
@ -496,9 +524,11 @@
"orange-dingos-poke",
"orange-laws-drop",
"orange-masks-exercise",
"orange-sheep-exist",
"orange-yaks-protect",
"orange-zoos-heal",
"perfect-actors-bake",
"perfect-cooks-shop",
"perfect-hairs-matter",
"perfect-hats-dance",
"perfect-pugs-shake",
@ -514,6 +544,7 @@
"plenty-zoos-fix",
"polite-dolphins-care",
"polite-peas-mate",
"polite-pugs-attend",
"polite-pumpkins-guess",
"polite-ravens-study",
"polite-tomatoes-trade",
@ -533,6 +564,7 @@
"popular-roses-teach",
"popular-walls-hunt",
"pretty-ties-help",
"proud-lobsters-lie",
"proud-pets-hang",
"proud-queens-sniff",
"purple-dragons-peel",
@ -558,6 +590,8 @@
"red-doors-own",
"red-feet-worry",
"red-kings-draw",
"red-ladybugs-turn",
"red-maps-nail",
"red-poets-study",
"red-pots-pretend",
"rich-cobras-exist",
@ -570,6 +604,7 @@
"rich-tables-sing",
"rich-taxis-hear",
"rich-waves-mix",
"rotten-actors-rush",
"rotten-bags-type",
"rotten-buckets-develop",
"rotten-experts-relax",
@ -581,6 +616,7 @@
"selfish-socks-smile",
"selfish-spies-help",
"selfish-tools-hide",
"selfish-trainers-kiss",
"serious-bobcats-carry",
"serious-crabs-punch",
"serious-goats-tap",
@ -597,6 +633,7 @@
"seven-hornets-smile",
"seven-jobs-sniff",
"seven-masks-end",
"seven-news-live",
"seven-ravens-check",
"shaggy-cameras-live",
"shaggy-comics-jog",
@ -626,6 +663,7 @@
"silent-rabbits-join",
"silent-rocks-yell",
"silly-ducks-mix",
"silly-fishes-deny",
"silly-laws-happen",
"silly-lies-film",
"silly-masks-exist",
@ -634,6 +672,7 @@
"silver-points-approve",
"silver-sheep-knock",
"six-apes-peel",
"six-beans-laugh",
"six-bears-trade",
"six-boats-shave",
"six-chicken-kneel",
@ -649,6 +688,7 @@
"slimy-clouds-talk",
"slimy-hairs-impress",
"slimy-laws-explode",
"slimy-news-help",
"slimy-onions-approve",
"slimy-walls-draw",
"slow-beds-shave",
@ -707,16 +747,19 @@
"stale-fans-rest",
"stale-jeans-refuse",
"stale-nails-listen",
"stale-rats-check",
"strange-apricots-happen",
"strange-pears-perform",
"strange-pillows-greet",
"strange-roses-brake",
"strange-trains-destroy",
"strong-apricots-destroy",
"strong-gifts-smoke",
"strong-lemons-provide",
"strong-pans-doubt",
"stupid-bottles-lay",
"stupid-cars-behave",
"stupid-dodos-hide",
"stupid-parents-crash",
"stupid-rivers-stare",
"sweet-bottles-check",
@ -740,6 +783,7 @@
"tame-cycles-kneel",
"tame-dodos-float",
"tame-dots-battle",
"tame-frogs-shave",
"tame-goats-bow",
"tame-spies-drum",
"tasty-cheetahs-appear",
@ -751,6 +795,7 @@
"ten-jokes-divide",
"ten-peaches-sleep",
"ten-singers-cough",
"ten-singers-repair",
"ten-teachers-travel",
"ten-ties-repair",
"ten-trainers-juggle",
@ -796,6 +841,7 @@
"tiny-taxis-whisper",
"tough-buckets-thank",
"tough-radios-punch",
"tough-snails-chew",
"tough-tomatoes-explain",
"tricky-avocados-play",
"tricky-balloons-care",
@ -807,8 +853,10 @@
"twelve-dragons-join",
"twelve-onions-juggle",
"twelve-scissors-kneel",
"twelve-shrimps-run",
"twelve-worms-jog",
"twenty-gifts-develop",
"twenty-toes-attack",
"two-brooms-fail",
"two-candles-move",
"two-cats-approve",
@ -820,6 +868,7 @@
"unlucky-points-clap",
"unlucky-steaks-warn",
"unlucky-trees-lick",
"violet-bats-brake",
"violet-mails-trade",
"violet-mugs-behave",
"violet-otters-carry",
@ -848,6 +897,7 @@
"wicked-years-drive",
"wild-cows-chew",
"wild-foxes-wonder",
"wild-mice-look",
"wild-moose-compare",
"wild-moose-destroy",
"wild-poems-design",
@ -876,6 +926,7 @@
"yellow-taxis-double",
"yellow-trees-juggle",
"young-ads-roll",
"young-feet-double",
"young-masks-refuse",
"young-peaches-agree"
]

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: Ensure imports are above other statements

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: treat pure call expressions as potentially reactive if they reference local bindings

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: better generated each block code in SSR mode

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure unowned derived signals correctly re-connect to graph

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure inner script tags are properly removed

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure event currentTarget is reset after propagation logic

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow custom element styles to be updated in HMR mode

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent binding to imports

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: never abort bidirectional transitions

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: follow spec for `customElement` option

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: improve ssr parent validation

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: unwrap function expressions where possible, and optimise bindings

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent nullish snippet for rendering empty content

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: allow more characters in the unicode range as component identifiers

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: allow for nesting selector in pseudoclasses

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: tighten up `$` prefix validation

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure StyleDirective and ClassDirective are marked as dynamic

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: add infinite loop effect callstack

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: inject styles correctly when mounting inside an iframe

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: provide AST node types with internal types stripped out

@ -29,9 +29,9 @@ jobs:
os: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
@ -44,9 +44,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18
cache: pnpm
@ -64,9 +64,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 18
cache: pnpm

@ -18,13 +18,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
- uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18.x
cache: pnpm

@ -102,6 +102,7 @@ The `t` argument passed to `css` is a value between `0` and `1` after the `easin
The function is called repeatedly _before_ the transition begins, with different `t` and `u` arguments.
```svelte
<!--- file: App.svelte --->
<script>
import { elasticOut } from 'svelte/easing';
@ -345,6 +346,7 @@ The function is called repeatedly _before_ the animation begins, with different
<!-- TODO: Types -->
```svelte
<!--- file: App.svelte --->
<script>
import { cubicOut } from 'svelte/easing';
@ -378,6 +380,7 @@ A custom animation function can also return a `tick` function, which is called _
> If it's possible to use `css` instead of `tick`, do so — CSS animations can run off the main thread, preventing jank on slower devices.
```svelte
<!--- file: App.svelte --->
<script>
import { cubicOut } from 'svelte/easing';

@ -1,4 +1,4 @@
---
---
title: Bindings
---
@ -42,25 +42,34 @@ Numeric input values are coerced; even though `input.value` is a string as far a
<input type="range" bind:value={num} />
```
On `<input>` elements with `type="file"`, you can use `bind:files` to get the [`FileList` of selected files](https://developer.mozilla.org/en-US/docs/Web/API/FileList). It is readonly.
On `<input>` elements with `type="file"`, you can use `bind:files` to get the [`FileList` of selected files](https://developer.mozilla.org/en-US/docs/Web/API/FileList). When you want to update the files programmatically, you always need to use a `FileList` object. Currently `FileList` objects cannot be constructed directly, so you need to create a new [`DataTransfer`](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer) object and get `files` from there.
```svelte
<script>
let files = $state();
function clear() {
files = new DataTransfer().files; // null or undefined does not work
}
</script>
<label for="avatar">Upload a picture:</label>
<input accept="image/png, image/jpeg" bind:files id="avatar" name="avatar" type="file" />
<button onclick={clear}>clear</button>
```
If you're using `bind:` directives together with `on:` directives, the order that they're defined in affects the value of the bound variable when the event handler is called.
`FileList` objects also cannot be modified, so if you want to e.g. delete a single file from the list, you need to create a new `DataTransfer` object and add the files you want to keep.
> `DataTransfer` may not be available in server-side JS runtimes. Leaving the state that is bound to `files` uninitialized prevents potential errors if components are server-side rendered.
If you're using `bind:` directives together with `on` event attributes, the binding will always fire before the event attribute.
```svelte
<script>
let value = 'Hello World';
</script>
<input
on:input={() => console.log('Old value:', value)}
bind:value
on:input={() => console.log('New value:', value)}
/>
<input oninput={() => console.log('New value:', value)} bind:value />
```
Here we were binding to the value of a text input, which uses the `input` event. Bindings on other elements may use different events such as `change`.

@ -14,8 +14,7 @@ It cannot appear at the top level of your markup; it must be inside an if or eac
```svelte
<script>
/** @type {number} */
export let count;
let { count } = $props();
</script>
{#if count > 0}
@ -56,12 +55,12 @@ If `this` is the name of a [void element](https://developer.mozilla.org/en-US/do
```svelte
<script>
let tag = 'div';
let { handler } = $props();
export let handler;
let tag = $state('div');
</script>
<svelte:element this={tag} on:click={handler}>Foo</svelte:element>
<svelte:element this={tag} onclick={handler}>Foo</svelte:element>
```
Svelte tries its best to infer the correct namespace from the element's surroundings, but it's not always possible. You can make it explicit with an `xmlns` attribute:
@ -73,7 +72,7 @@ Svelte tries its best to infer the correct namespace from the element's surround
## `<svelte:window>`
```svelte
<svelte:window on:event={handler} />
<svelte:window onevent={handler} />
```
```svelte
@ -86,13 +85,12 @@ Unlike `<svelte:self>`, this element may only appear at the top level of your co
```svelte
<script>
/** @param {KeyboardEvent} event */
function handleKeydown(event) {
alert(`pressed the ${event.key} key`);
}
</script>
<svelte:window on:keydown={handleKeydown} />
<svelte:window onkeydown={handleKeydown} />
```
You can also bind to the following properties:
@ -117,7 +115,7 @@ All except `scrollX` and `scrollY` are readonly.
## `<svelte:document>`
```svelte
<svelte:document on:event={handler} />
<svelte:document onevent={handler} />
```
```svelte
@ -129,7 +127,7 @@ Similarly to `<svelte:window>`, this element allows you to add listeners to even
As with `<svelte:window>`, this element may only appear the top level of your component and must never be inside a block or element.
```svelte
<svelte:document on:visibilitychange={handleVisibilityChange} use:someAction />
<svelte:document onvisibilitychange={handleVisibilityChange} use:someAction />
```
You can also bind to the following properties:
@ -144,7 +142,7 @@ All are readonly.
## `<svelte:body>`
```svelte
<svelte:body on:event={handler} />
<svelte:body onevent={handler} />
```
Similarly to `<svelte:window>`, this element allows you to add listeners to events on `document.body`, such as `mouseenter` and `mouseleave`, which don't fire on `window`. It also lets you use [actions](/docs/element-directives#use-action) on the `<body>` element.
@ -152,7 +150,7 @@ Similarly to `<svelte:window>`, this element allows you to add listeners to even
As with `<svelte:window>` and `<svelte:document>`, this element may only appear the top level of your component and must never be inside a block or element.
```svelte
<svelte:body on:mouseenter={handleMouseenter} on:mouseleave={handleMouseleave} use:someAction />
<svelte:body onmouseenter={handleMouseenter} onmouseleave={handleMouseleave} use:someAction />
```
## `<svelte:head>`

@ -118,7 +118,7 @@ Components can declare a generic relationship between their properties. One exam
select: Item;
}
let { items, select } = $props();
let { items, select }: Props = $props();
</script>
{#each items as item}
@ -210,8 +210,8 @@ declare namespace svelteHTML {
}
// enhance attributes
interface HTMLAttributes<T> {
// If you want to use on:beforeinstallprompt
'on:beforeinstallprompt'?: (event: any) => any;
// If you want to use the beforeinstallprompt event
'onbeforeinstallprompt'?: (event: any) => any;
// If you want to use myCustomAttribute={..} (note: all lowercase)
mycustomattribute?: any; // You can replace any with something more specific if you like
}

@ -2,4 +2,4 @@
title: svelte
---
<!-- @include svelte -->
> MODULE: svelte

@ -2,4 +2,4 @@
title: svelte/action
---
<!-- @include svelte/action -->
> MODULE: svelte/action

@ -2,4 +2,4 @@
title: svelte/animate
---
<!-- @include svelte/animate -->
> MODULE: svelte/animate

@ -2,4 +2,4 @@
title: svelte/compiler
---
<!-- @include svelte/compiler -->
> MODULE: svelte/compiler

@ -2,4 +2,4 @@
title: svelte/easing
---
<!-- @include svelte/easing -->
> MODULE: svelte/easing

@ -2,4 +2,4 @@
title: svelte/events
---
<!-- @include svelte/events -->
> MODULE: svelte/events

@ -2,4 +2,4 @@
title: svelte/legacy
---
<!-- @include svelte/legacy -->
> MODULE: svelte/legacy

@ -2,4 +2,4 @@
title: svelte/motion
---
<!-- @include svelte/motion -->
> MODULE: svelte/motion

@ -2,4 +2,4 @@
title: svelte/reactivity
---
<!-- @include svelte/reactivity -->
> MODULE: svelte/reactivity

@ -2,4 +2,4 @@
title: svelte/server
---
<!-- @include svelte/server -->
> MODULE: svelte/server

@ -2,4 +2,4 @@
title: svelte/store
---
<!-- @include svelte/store -->
> MODULE: svelte/store

@ -2,4 +2,4 @@
title: svelte/transition
---
<!-- @include svelte/transition -->
> MODULE: svelte/transition

@ -29,8 +29,8 @@
"bench:debug": "node --allow-natives-syntax --inspect-brk ./benchmarking/run.js"
},
"devDependencies": {
"@changesets/cli": "^2.27.6",
"@sveltejs/eslint-config": "^8.0.1",
"@changesets/cli": "^2.27.8",
"@sveltejs/eslint-config": "^8.1.0",
"@svitejs/changesets-changelog-github-compact": "^1.1.0",
"@types/node": "^20.11.5",
"@vitest/coverage-v8": "^2.0.5",

@ -1,5 +1,147 @@
# svelte
## 5.0.0-next.249
### Patch Changes
- fix: ensure snapshot logs don't affect dependency graph ([#13286](https://github.com/sveltejs/svelte/pull/13286))
- fix: allow custom element styles to be updated in HMR mode ([#13225](https://github.com/sveltejs/svelte/pull/13225))
- fix: inject styles correctly when mounting inside an iframe ([#13225](https://github.com/sveltejs/svelte/pull/13225))
## 5.0.0-next.248
### Patch Changes
- feat: provide guidance in browser console when logging $state objects ([#13142](https://github.com/sveltejs/svelte/pull/13142))
- fix: ensure correct parent effect is associated with render effects ([#13274](https://github.com/sveltejs/svelte/pull/13274))
- feat: unwrap function expressions where possible, and optimise bindings ([#13269](https://github.com/sveltejs/svelte/pull/13269))
## 5.0.0-next.247
### Patch Changes
- fix: wait until template strings are complete before sanitizing ([#13262](https://github.com/sveltejs/svelte/pull/13262))
- fix: avoid flushing sync with $inspect ([#13239](https://github.com/sveltejs/svelte/pull/13239))
- fix: separate `template_effect` for dynamic class/style directive with dynamic attributes ([#13171](https://github.com/sveltejs/svelte/pull/13171))
- fix: treat pure call expressions as potentially reactive if they reference local bindings ([#13264](https://github.com/sveltejs/svelte/pull/13264))
- fix: follow spec for `customElement` option ([#13247](https://github.com/sveltejs/svelte/pull/13247))
- fix: tighten up `# svelte prefix validation ([#13261](https://github.com/sveltejs/svelte/pull/13261))
## 5.0.0-next.246
### Patch Changes
- perf: inline module variables into template ([#13075](https://github.com/sveltejs/svelte/pull/13075))
- fix: allow custom element events on slot to bubble inside custom element ([#13222](https://github.com/sveltejs/svelte/pull/13222))
- fix: add missing `autocomplete` attribute tokens ([#13229](https://github.com/sveltejs/svelte/pull/13229))
- feat: add infinite loop effect callstack ([#13231](https://github.com/sveltejs/svelte/pull/13231))
## 5.0.0-next.245
### Patch Changes
- fix: visit expression for `svelte:component` references ([#13151](https://github.com/sveltejs/svelte/pull/13151))
- fix: ensure signal graph is consistent before triggering $inspect signals ([#13153](https://github.com/sveltejs/svelte/pull/13153))
- feat: better types for the `autocomplete` attribute ([#13201](https://github.com/sveltejs/svelte/pull/13201))
- fix: widen ownership upon property access if necessary ([#13175](https://github.com/sveltejs/svelte/pull/13175))
- fix: don't show `state_referenced_locally` warning on types ([#13177](https://github.com/sveltejs/svelte/pull/13177))
- fix: ensure locally mutated bindable props persist with spreading props ([#13190](https://github.com/sveltejs/svelte/pull/13190))
- fix: try catch `strict_equals` to avoid error accessing `STATE_SYMBOL` ([#13216](https://github.com/sveltejs/svelte/pull/13216))
- fix: ensure types are easier to follow for TypeScript ([#13140](https://github.com/sveltejs/svelte/pull/13140))
- fix: ensure $inspect effects are fine-grain ([#13199](https://github.com/sveltejs/svelte/pull/13199))
- fix: ensure unowned derived signals correctly re-connect to graph ([#13184](https://github.com/sveltejs/svelte/pull/13184))
- fix: ensure inner script tags are properly removed ([#13152](https://github.com/sveltejs/svelte/pull/13152))
- chore: improve ssr parent validation ([#13158](https://github.com/sveltejs/svelte/pull/13158))
- fix: prevent nullish snippet for rendering empty content ([#13083](https://github.com/sveltejs/svelte/pull/13083))
- fix: allow more characters in the unicode range as component identifiers ([#13198](https://github.com/sveltejs/svelte/pull/13198))
- fix: allow for nesting selector in pseudoclasses ([#13209](https://github.com/sveltejs/svelte/pull/13209))
- fix: ensure StyleDirective and ClassDirective are marked as dynamic ([#13205](https://github.com/sveltejs/svelte/pull/13205))
## 5.0.0-next.244
### Patch Changes
- fix: error on duplicate style and class directive ([#13097](https://github.com/sveltejs/svelte/pull/13097))
- fix: ensure $host rune correctly compiles in variable declarations ([#13127](https://github.com/sveltejs/svelte/pull/13127))
- fix: remove unnecessary update assignments ([#13113](https://github.com/sveltejs/svelte/pull/13113))
- fix: error at compile time on unsupported TypeScript language features ([#12982](https://github.com/sveltejs/svelte/pull/12982))
- fix: Ensure imports are above other statements ([#13132](https://github.com/sveltejs/svelte/pull/13132))
## 5.0.0-next.243
### Patch Changes
- fix: ensure reactive graph is fully traversed in the marking phase for non-runes mode ([#13059](https://github.com/sveltejs/svelte/pull/13059))
- fix: ensure reactivity system remains consistent with removals ([#13087](https://github.com/sveltejs/svelte/pull/13087))
- fix: render undefined html as the empty string ([#13092](https://github.com/sveltejs/svelte/pull/13092))
- fix: error on incorrect attributes for svelte:body ([#13084](https://github.com/sveltejs/svelte/pull/13084))
- feat: provide AST node types with internal types stripped out ([#12968](https://github.com/sveltejs/svelte/pull/12968))
## 5.0.0-next.242
### Patch Changes
- fix: insert comment before text in an each block, to prevent glued nodes ([#13073](https://github.com/sveltejs/svelte/pull/13073))
- feat: better generated each block code in SSR mode ([#13060](https://github.com/sveltejs/svelte/pull/13060))
## 5.0.0-next.241
### Patch Changes
- fix: prevent div/0 when generating transition keyframes ([#13058](https://github.com/sveltejs/svelte/pull/13058))
- fix: error on invalid element name ([#13057](https://github.com/sveltejs/svelte/pull/13057))
- fix: better compile errors for invalid tag names/placement ([#13045](https://github.com/sveltejs/svelte/pull/13045))
- fix: ensure event currentTarget is reset after propagation logic ([#13042](https://github.com/sveltejs/svelte/pull/13042))
## 5.0.0-next.240
### Patch Changes
- fix: use WAAPI to control timing of JS-based animations ([#13018](https://github.com/sveltejs/svelte/pull/13018))
- fix: prevent binding to imports ([#13035](https://github.com/sveltejs/svelte/pull/13035))
- fix: never abort bidirectional transitions ([#13018](https://github.com/sveltejs/svelte/pull/13018))
## 5.0.0-next.239
### Patch Changes

@ -61,6 +61,17 @@ export type TransitionEventHandler<T extends EventTarget> = EventHandler<Transit
export type MessageEventHandler<T extends EventTarget> = EventHandler<MessageEvent, T>;
export type ToggleEventHandler<T extends EventTarget> = EventHandler<ToggleEvent, T>;
export type FullAutoFill =
| AutoFill
| 'bday'
| `${OptionalPrefixToken<AutoFillAddressKind>}${'cc-additional-name'}`
| 'nickname'
| 'language'
| 'organization-title'
| 'photo'
| 'sex'
| 'url';
//
// DOM Attributes
// ----------------------------------------------------------------------
@ -937,7 +948,7 @@ export interface HTMLFieldsetAttributes extends HTMLAttributes<HTMLFieldSetEleme
export interface HTMLFormAttributes extends HTMLAttributes<HTMLFormElement> {
acceptcharset?: string | undefined | null;
action?: string | undefined | null;
autocomplete?: string | undefined | null;
autocomplete?: AutoFillBase | undefined | null;
enctype?: string | undefined | null;
method?: string | undefined | null;
name?: string | undefined | null;
@ -1025,7 +1036,7 @@ export type HTMLInputTypeAttribute =
export interface HTMLInputAttributes extends HTMLAttributes<HTMLInputElement> {
accept?: string | undefined | null;
alt?: string | undefined | null;
autocomplete?: string | undefined | null;
autocomplete?: FullAutoFill | undefined | null;
capture?: boolean | 'user' | 'environment' | undefined | null; // https://www.w3.org/TR/html-media-capture/#the-capture-attribute
checked?: boolean | undefined | null;
dirname?: string | undefined | null;
@ -1244,7 +1255,7 @@ export interface HTMLScriptAttributes extends HTMLAttributes<HTMLScriptElement>
}
export interface HTMLSelectAttributes extends HTMLAttributes<HTMLSelectElement> {
autocomplete?: string | undefined | null;
autocomplete?: FullAutoFill | undefined | null;
disabled?: boolean | undefined | null;
form?: string | undefined | null;
multiple?: boolean | undefined | null;
@ -1289,7 +1300,7 @@ export interface HTMLTableAttributes extends HTMLAttributes<HTMLTableElement> {
}
export interface HTMLTextareaAttributes extends HTMLAttributes<HTMLTextAreaElement> {
autocomplete?: string | undefined | null;
autocomplete?: FullAutoFill | undefined | null;
cols?: number | undefined | null;
dirname?: string | undefined | null;
disabled?: boolean | undefined | null;
@ -1934,7 +1945,7 @@ export interface SvelteHTMLElements {
| string
| undefined
| {
tag: string;
tag?: string;
shadow?: 'open' | 'none' | undefined;
props?:
| Record<

@ -48,6 +48,10 @@
> Failed to hydrate the application
## invalid_snippet
> Could not `{@render}` snippet due to the expression being `null` or `undefined`. Consider using optional chaining `{@render snippet?.()}`
## lifecycle_legacy_only
> `%name%(...)` cannot be used in runes mode

@ -4,6 +4,14 @@
> `%binding%` (%location%) is binding to a non-reactive property
## console_log_state
> Your `console.%method%` contained `$state` proxies. Consider using `$inspect(...)` or `$state.snapshot(...)` instead
When logging a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), browser devtools will log the proxy itself rather than the value it represents. In the case of Svelte, the 'target' of a `$state` proxy might not resemble its current value, which can be confusing.
The easiest way to log a value as it changes over time is to use the [`$inspect`](https://svelte-5-preview.vercel.app/docs/runes#$inspect) rune. Alternatively, to log things on a one-off basis (for example, inside an event handler) you can use [`$state.snapshot`](https://svelte-5-preview.vercel.app/docs/runes#$state-snapshot) to take a snapshot of the current value.
## event_handler_invalid
> %handler% should be a function. Did you mean to %suggestion%?

@ -159,3 +159,7 @@
> Cannot reference store value outside a `.svelte` file
Using a `$` prefix to refer to the value of a store is only possible inside `.svelte` files, where Svelte can automatically create subscriptions when a component is mounted and unsubscribe when the component is unmounted. Consider migrating to runes instead.
## typescript_invalid_feature
> TypeScript language features like %feature% are not natively supported, and their use is generally discouraged. Outside of `<script>` tags, these features are not supported. For use within `<script>` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using `vitePreprocess`, make sure to specifically enable preprocessing script tags (`vitePreprocess({ script: true })`)

@ -40,7 +40,7 @@
## css_nesting_selector_invalid_placement
> Nesting selectors can only be used inside a rule
> Nesting selectors can only be used inside a rule or as the first selector inside a lone `:global(...)`
## css_selector_invalid

@ -100,10 +100,6 @@
> This type of directive is not valid on components
## component_invalid_name
> Component name must be a valid variable name or dot notation expression
## const_tag_cycle
> Cyclical dependency detected: %cycle%
@ -136,10 +132,6 @@
> `</%name%>` attempted to close element that was already automatically closed by `<%reason%>` (cannot nest `<%reason%>` inside `<%name%>`)
## element_invalid_tag_name
> Expected valid tag name
## element_unclosed
> `<%name%>` was left open
@ -282,6 +274,10 @@ HTML restricts where certain elements can appear. In case of a violation the bro
> A component can have a single top-level `<style>` element
## svelte_body_illegal_attribute
> `<svelte:body>` does not support non-event attributes or spread attributes
## svelte_component_invalid_this
> Invalid component definition — must be an `{expression}`
@ -348,7 +344,15 @@ HTML restricts where certain elements can appear. In case of a violation the bro
## svelte_options_invalid_tagname
> Tag name must be two or more words joined by the "-" character
> Tag name must be lowercase and hyphenated
See https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name for more information on valid tag names
## svelte_options_reserved_tagname
> Tag name is reserved
See https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name for more information on valid tag names
## svelte_options_unknown_attribute
@ -358,6 +362,10 @@ HTML restricts where certain elements can appear. In case of a violation the bro
> `<svelte:self>` components can only exist inside `{#if}` blocks, `{#each}` blocks, `{#snippet}` blocks or slots passed to components
## tag_invalid_name
> Expected a valid element or component name. Components must have a valid variable name or dot notation expression
## tag_invalid_placement
> {@%name% ...} tag cannot be %location%

@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.0.0-next.239",
"version": "5.0.0-next.249",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
@ -123,7 +123,7 @@
"@rollup/plugin-virtual": "^3.0.2",
"@types/aria-query": "^5.0.4",
"@types/node": "^20.11.5",
"dts-buddy": "^0.5.1",
"dts-buddy": "^0.5.3",
"esbuild": "^0.21.5",
"rollup": "^4.21.0",
"source-map": "^0.7.4",
@ -137,7 +137,7 @@
"@types/estree": "^1.0.5",
"acorn": "^8.12.1",
"acorn-typescript": "^1.4.13",
"aria-query": "^5.3.0",
"aria-query": "^5.3.1",
"axobject-query": "^4.1.0",
"esm-env": "^1.0.0",
"esrap": "^1.2.2",

@ -1,5 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { rollup } from 'rollup';
import virtual from '@rollup/plugin-virtual';
import { nodeResolve } from '@rollup/plugin-node-resolve';

@ -1,4 +1,5 @@
import fs from 'node:fs';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { createBundle } from 'dts-buddy';
@ -21,6 +22,10 @@ fs.writeFileSync(`${dir}/types/compiler/interfaces.d.ts`, "import '../index.js';
await createBundle({
output: `${dir}/types/index.d.ts`,
compilerOptions: {
// so that types/properties with `@internal` (and its dependencies) are removed from the output
stripInternal: true
},
modules: {
[pkg.name]: `${dir}/src/index.d.ts`,
[`${pkg.name}/action`]: `${dir}/src/action/public.d.ts`,
@ -33,7 +38,7 @@ await createBundle({
[`${pkg.name}/server`]: `${dir}/src/server/index.d.ts`,
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,
[`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`,
[`${pkg.name}/events`]: `${dir}/src/events/index.js`,
[`${pkg.name}/events`]: `${dir}/src/events/public.d.ts`,
// TODO remove in Svelte 6
[`${pkg.name}/types/compiler/preprocess`]: `${dir}/src/compiler/preprocess/legacy-public.d.ts`,
[`${pkg.name}/types/compiler/interfaces`]: `${dir}/src/compiler/types/legacy-interfaces.d.ts`
@ -56,3 +61,14 @@ if (bad_links.length > 0) {
process.exit(1);
}
if (types.includes('\texport { ')) {
// eslint-disable-next-line no-console
console.error(
`The generated types file should not contain 'export { ... }' statements. ` +
`TypeScript is bad at following these: when creating d.ts files through @sveltejs/package, and one of these types is used, ` +
`TypeScript will likely fail at generating a d.ts file. ` +
`To prevent this, do 'export interface Foo {}' instead of 'interface Foo {}' and then 'export { Foo }'`
);
process.exit(1);
}

@ -434,6 +434,16 @@ export function store_invalid_subscription_module(node) {
e(node, "store_invalid_subscription_module", "Cannot reference store value outside a `.svelte` file");
}
/**
* TypeScript language features like %feature% are not natively supported, and their use is generally discouraged. Outside of `<script>` tags, these features are not supported. For use within `<script>` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using `vitePreprocess`, make sure to specifically enable preprocessing script tags (`vitePreprocess({ script: true })`)
* @param {null | number | NodeLike} node
* @param {string} feature
* @returns {never}
*/
export function typescript_invalid_feature(node, feature) {
e(node, "typescript_invalid_feature", `TypeScript language features like ${feature} are not natively supported, and their use is generally discouraged. Outside of \`<script>\` tags, these features are not supported. For use within \`<script>\` tags, you will need to use a preprocessor to convert it to JavaScript before it gets passed to the Svelte compiler. If you are using \`vitePreprocess\`, make sure to specifically enable preprocessing script tags (\`vitePreprocess({ script: true })\`)`);
}
/**
* Declaration cannot be empty
* @param {null | number | NodeLike} node
@ -526,12 +536,12 @@ export function css_global_invalid_selector_list(node) {
}
/**
* Nesting selectors can only be used inside a rule
* Nesting selectors can only be used inside a rule or as the first selector inside a lone `:global(...)`
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function css_nesting_selector_invalid_placement(node) {
e(node, "css_nesting_selector_invalid_placement", "Nesting selectors can only be used inside a rule");
e(node, "css_nesting_selector_invalid_placement", "Nesting selectors can only be used inside a rule or as the first selector inside a lone `:global(...)`");
}
/**
@ -786,15 +796,6 @@ export function component_invalid_directive(node) {
e(node, "component_invalid_directive", "This type of directive is not valid on components");
}
/**
* Component name must be a valid variable name or dot notation expression
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function component_invalid_name(node) {
e(node, "component_invalid_name", "Component name must be a valid variable name or dot notation expression");
}
/**
* Cyclical dependency detected: %cycle%
* @param {null | number | NodeLike} node
@ -872,15 +873,6 @@ export function element_invalid_closing_tag_autoclosed(node, name, reason) {
e(node, "element_invalid_closing_tag_autoclosed", `\`</${name}>\` attempted to close element that was already automatically closed by \`<${reason}>\` (cannot nest \`<${reason}>\` inside \`<${name}>\`)`);
}
/**
* Expected valid tag name
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function element_invalid_tag_name(node) {
e(node, "element_invalid_tag_name", "Expected valid tag name");
}
/**
* `<%name%>` was left open
* @param {null | number | NodeLike} node
@ -1201,6 +1193,15 @@ export function style_duplicate(node) {
e(node, "style_duplicate", "A component can have a single top-level `<style>` element");
}
/**
* `<svelte:body>` does not support non-event attributes or spread attributes
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function svelte_body_illegal_attribute(node) {
e(node, "svelte_body_illegal_attribute", "`<svelte:body>` does not support non-event attributes or spread attributes");
}
/**
* Invalid component definition must be an `{expression}`
* @param {null | number | NodeLike} node
@ -1351,12 +1352,21 @@ export function svelte_options_invalid_customelement_shadow(node) {
}
/**
* Tag name must be two or more words joined by the "-" character
* Tag name must be lowercase and hyphenated
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function svelte_options_invalid_tagname(node) {
e(node, "svelte_options_invalid_tagname", "Tag name must be two or more words joined by the \"-\" character");
e(node, "svelte_options_invalid_tagname", "Tag name must be lowercase and hyphenated");
}
/**
* Tag name is reserved
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function svelte_options_reserved_tagname(node) {
e(node, "svelte_options_reserved_tagname", "Tag name is reserved");
}
/**
@ -1378,6 +1388,15 @@ export function svelte_self_invalid_placement(node) {
e(node, "svelte_self_invalid_placement", "`<svelte:self>` components can only exist inside `{#if}` blocks, `{#each}` blocks, `{#snippet}` blocks or slots passed to components");
}
/**
* Expected a valid element or component name. Components must have a valid variable name or dot notation expression
* @param {null | number | NodeLike} node
* @returns {never}
*/
export function tag_invalid_name(node) {
e(node, "tag_invalid_name", "Expected a valid element or component name. Components must have a valid variable name or dot notation expression");
}
/**
* {@%name% ...} tag cannot be %location%
* @param {null | number | NodeLike} node

@ -1,5 +1,6 @@
/** @import { LegacyRoot } from './types/legacy-nodes.js' */
/** @import { CompileOptions, CompileResult, ValidatedCompileOptions, ModuleCompileOptions, Root } from '#compiler' */
/** @import { CompileOptions, CompileResult, ValidatedCompileOptions, ModuleCompileOptions } from '#compiler' */
/** @import { AST } from './public.js' */
import { walk as zimmerframe_walk } from 'zimmerframe';
import { convert } from './legacy.js';
import { parse as parse_acorn } from './phases/1-parse/acorn.js';
@ -77,7 +78,7 @@ export function compileModule(source, options) {
* @overload
* @param {string} source
* @param {{ filename?: string; modern: true }} options
* @returns {Root}
* @returns {AST.Root}
*/
/**
@ -90,7 +91,7 @@ export function compileModule(source, options) {
* @overload
* @param {string} source
* @param {{ filename?: string; modern?: false }} [options]
* @returns {LegacyRoot}
* @returns {Record<string, any>}
*/
/**
@ -102,7 +103,7 @@ export function compileModule(source, options) {
* https://svelte.dev/docs/svelte-compiler#svelte-parse
* @param {string} source
* @param {{ filename?: string; rootDir?: string; modern?: boolean }} [options]
* @returns {Root | LegacyRoot}
* @returns {AST.Root | LegacyRoot}
*/
export function parse(source, { filename, rootDir, modern } = {}) {
state.reset_warning_filter(() => false);
@ -114,7 +115,7 @@ export function parse(source, { filename, rootDir, modern } = {}) {
/**
* @param {string} source
* @param {Root} ast
* @param {AST.Root} ast
* @param {boolean | undefined} modern
*/
function to_public_ast(source, ast, modern) {

@ -1,5 +1,5 @@
/** @import { Expression } from 'estree' */
/** @import { BaseNode, ConstTag, Root, SvelteNode, TemplateNode, Text } from '#compiler' */
/** @import { AST, SvelteNode, TemplateNode } from '#compiler' */
/** @import * as Legacy from './types/legacy-nodes.js' */
import { walk } from 'zimmerframe';
import {
@ -36,7 +36,7 @@ function remove_surrounding_whitespace_nodes(nodes) {
/**
* Transform our nice modern AST into the monstrosity emitted by Svelte 4
* @param {string} source
* @param {Root} ast
* @param {AST.Root} ast
* @returns {Legacy.LegacyRoot}
*/
export function convert(source, ast) {
@ -74,8 +74,8 @@ export function convert(source, ast) {
let end = null;
if (node.fragment.nodes.length > 0) {
const first = /** @type {BaseNode} */ (node.fragment.nodes.at(0));
const last = /** @type {BaseNode} */ (node.fragment.nodes.at(-1));
const first = /** @type {AST.BaseNode} */ (node.fragment.nodes.at(0));
const last = /** @type {AST.BaseNode} */ (node.fragment.nodes.at(-1));
start = first.start;
end = last.end;
@ -242,7 +242,7 @@ export function convert(source, ast) {
return node;
}
const modern_node = /** @type {ConstTag} */ (node);
const modern_node = /** @type {AST.ConstTag} */ (node);
const { id: left } = { ...modern_node.declaration.declarations[0] };
// @ts-ignore
delete left.typeAnnotation;
@ -584,7 +584,7 @@ export function convert(source, ast) {
const parent = path.at(-1);
if (parent?.type === 'RegularElement' && parent.name === 'style') {
// these text nodes are missing `raw` for some dumb reason
return /** @type {Text} */ ({
return /** @type {AST.Text} */ ({
type: 'Text',
start: node.start,
end: node.end,

@ -2,7 +2,7 @@
/** @import { Visitors } from 'zimmerframe' */
/** @import { ComponentAnalysis } from '../phases/types.js' */
/** @import { Scope } from '../phases/scope.js' */
/** @import * as Compiler from '#compiler' */
/** @import { AST, Binding, SvelteNode, ValidatedCompileOptions } from '#compiler' */
import MagicString from 'magic-string';
import { walk } from 'zimmerframe';
import { parse } from '../phases/1-parse/index.js';
@ -30,7 +30,7 @@ export function migrate(source) {
const { customElement: customElementOptions, ...parsed_options } = parsed.options || {};
/** @type {Compiler.ValidatedCompileOptions} */
/** @type {ValidatedCompileOptions} */
const combined_options = {
...validate_component_options({}, ''),
...parsed_options,
@ -160,7 +160,7 @@ export function migrate(source) {
let needs_reordering = false;
for (const [node, { dependencies }] of state.analysis.reactive_statements) {
/** @type {Compiler.Binding[]} */
/** @type {Binding[]} */
let ids = [];
if (
node.body.type === 'ExpressionStatement' &&
@ -229,7 +229,7 @@ export function migrate(source) {
* }} State
*/
/** @type {Visitors<Compiler.SvelteNode, State>} */
/** @type {Visitors<SvelteNode, State>} */
const instance_script = {
_(node, { state, next }) {
// @ts-expect-error
@ -335,7 +335,7 @@ const instance_script = {
// }
}
const binding = /** @type {Compiler.Binding} */ (state.scope.get(declarator.id.name));
const binding = /** @type {Binding} */ (state.scope.get(declarator.id.name));
if (state.analysis.uses_props && (declarator.init || binding.updated)) {
throw new Error(
@ -478,7 +478,7 @@ const instance_script = {
}
};
/** @type {Visitors<Compiler.SvelteNode, State>} */
/** @type {Visitors<SvelteNode, State>} */
const template = {
Identifier(node, { state, path }) {
handle_identifier(node, state, path);
@ -596,7 +596,7 @@ const template = {
/**
* @param {VariableDeclarator} declarator
* @param {MagicString} str
* @param {Array<Compiler.SvelteNode>} path
* @param {SvelteNode[]} path
*/
function extract_type_and_comment(declarator, str, path) {
const parent = path.at(-1);
@ -641,11 +641,11 @@ function extract_type_and_comment(declarator, str, path) {
}
/**
* @param {Compiler.RegularElement | Compiler.SvelteElement | Compiler.SvelteWindow | Compiler.SvelteDocument | Compiler.SvelteBody} element
* @param {AST.RegularElement | AST.SvelteElement | AST.SvelteWindow | AST.SvelteDocument | AST.SvelteBody} element
* @param {State} state
*/
function handle_events(element, state) {
/** @type {Map<string, Compiler.OnDirective[]>} */
/** @type {Map<string, AST.OnDirective[]>} */
const handlers = new Map();
for (const attribute of element.attributes) {
if (attribute.type !== 'OnDirective') continue;
@ -880,7 +880,7 @@ function get_node_range(source, node) {
}
/**
* @param {Compiler.OnDirective} last
* @param {AST.OnDirective} last
* @param {State} state
*/
function generate_event_name(last, state) {
@ -927,6 +927,9 @@ function handle_identifier(node, state, path) {
} else if (node.name === '$$slots' && state.analysis.uses_slots) {
if (parent?.type === 'MemberExpression') {
state.str.update(/** @type {number} */ (node.start), parent.property.start, '');
if (parent.property.name === 'default') {
state.str.update(parent.property.start, parent.property.end, 'children');
}
}
// else passed as identifier, we don't know what to do here, so let it error
}

@ -1,4 +1,4 @@
/** @import { TemplateNode, Fragment, Root, SvelteOptionsRaw } from '#compiler' */
/** @import { AST, TemplateNode } from '#compiler' */
// @ts-expect-error acorn type definitions are borked in the release we use
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fragment from './state/fragment.js';
@ -8,6 +8,7 @@ import * as e from '../../errors.js';
import { create_fragment } from './utils/create.js';
import read_options from './read/options.js';
import { is_reserved } from '../../../utils.js';
import { disallow_children } from '../2-analyze/visitors/shared/special-element.js';
const regex_position_indicator = / \(\d+:\d+\)$/;
@ -30,10 +31,10 @@ export class Parser {
/** @type {TemplateNode[]} */
stack = [];
/** @type {Fragment[]} */
/** @type {AST.Fragment[]} */
fragments = [];
/** @type {Root} */
/** @type {AST.Root} */
root;
/** @type {Record<string, boolean>} */
@ -121,9 +122,12 @@ export class Parser {
(thing) => thing.type === 'SvelteOptions'
);
if (options_index !== -1) {
const options = /** @type {SvelteOptionsRaw} */ (this.root.fragment.nodes[options_index]);
const options = /** @type {AST.SvelteOptionsRaw} */ (this.root.fragment.nodes[options_index]);
this.root.fragment.nodes.splice(options_index, 1);
this.root.options = read_options(options);
disallow_children(options);
// We need this for the old AST format
Object.defineProperty(this.root.options, '__raw__', {
value: options,
@ -288,7 +292,7 @@ export class Parser {
/**
* @param {string} template
* @returns {Root}
* @returns {AST.Root}
*/
export function parse(template) {
const parser = new Parser(template);

@ -1,16 +1,14 @@
/** @import { ObjectExpression } from 'estree' */
/** @import { SvelteOptionsRaw, Root, SvelteOptions } from '#compiler' */
/** @import { AST } from '#compiler' */
import { NAMESPACE_MATHML, NAMESPACE_SVG } from '../../../../constants.js';
import * as e from '../../../errors.js';
const regex_valid_tag_name = /^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/;
/**
* @param {SvelteOptionsRaw} node
* @returns {Root['options']}
* @param {AST.SvelteOptionsRaw} node
* @returns {AST.Root['options']}
*/
export default function read_options(node) {
/** @type {SvelteOptions} */
/** @type {AST.SvelteOptions} */
const component_options = {
start: node.start,
end: node.end,
@ -39,7 +37,7 @@ export default function read_options(node) {
break; // eslint doesn't know this is unnecessary
}
case 'customElement': {
/** @type {SvelteOptions['customElement']} */
/** @type {AST.SvelteOptions['customElement']} */
const ce = {};
const { value: v } = attribute;
const value = v === true || Array.isArray(v) ? v : [v];
@ -229,6 +227,21 @@ function get_boolean_value(attribute) {
return value;
}
// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
const tag_name_char =
'[a-z0-9_.\xB7\xC0-\xD6\xD8-\xF6\xF8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}-]';
const regex_valid_tag_name = new RegExp(`^[a-z]${tag_name_char}*-${tag_name_char}*$`, 'u');
const reserved_tag_names = [
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph'
];
/**
* @param {any} attribute
* @param {string | null} tag
@ -238,7 +251,11 @@ function validate_tag(attribute, tag) {
if (typeof tag !== 'string') {
e.svelte_options_invalid_tagname(attribute);
}
if (tag && !regex_valid_tag_name.test(tag)) {
e.svelte_options_invalid_tagname(attribute);
if (tag) {
if (!regex_valid_tag_name.test(tag)) {
e.svelte_options_invalid_tagname(attribute);
} else if (reserved_tag_names.includes(tag)) {
e.svelte_options_reserved_tagname(attribute);
}
}
}

@ -1,5 +1,5 @@
/** @import { Program } from 'estree' */
/** @import { Attribute, SpreadAttribute, Directive, Script } from '#compiler' */
/** @import { AST, Directive } from '#compiler' */
/** @import { Parser } from '../index.js' */
import * as acorn from '../acorn.js';
import { regex_not_newline_characters } from '../../patterns.js';
@ -16,8 +16,8 @@ const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module'];
/**
* @param {Parser} parser
* @param {number} start
* @param {Array<Attribute | SpreadAttribute | Directive>} attributes
* @returns {Script}
* @param {Array<AST.Attribute | AST.SpreadAttribute | Directive>} attributes
* @returns {AST.Script}
*/
export function read_script(parser, start, attributes) {
const script_start = parser.index;
@ -45,7 +45,7 @@ export function read_script(parser, start, attributes) {
/** @type {'default' | 'module'} */
let context = 'default';
for (const attribute of /** @type {Attribute[]} */ (attributes)) {
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
if (RESERVED_ATTRIBUTES.includes(attribute.name)) {
e.script_reserved_attribute(attribute, attribute.name);
}

@ -1,4 +1,4 @@
/** @import { Attribute, SpreadAttribute, Directive, Css } from '#compiler' */
/** @import { AST, Css, Directive } from '#compiler' */
/** @import { Parser } from '../index.js' */
import * as e from '../../../errors.js';
@ -18,7 +18,7 @@ const REGEX_HTML_COMMENT_CLOSE = /-->/;
/**
* @param {Parser} parser
* @param {number} start
* @param {Array<Attribute | SpreadAttribute | Directive>} attributes
* @param {Array<AST.Attribute | AST.SpreadAttribute | Directive>} attributes
* @returns {Css.StyleSheet}
*/
export default function read_style(parser, start, attributes) {

@ -2,6 +2,7 @@
/** @import { FunctionExpression, FunctionDeclaration } from 'estree' */
import { walk } from 'zimmerframe';
import * as b from '../../utils/builders.js';
import * as e from '../../errors.js';
/**
* @param {FunctionExpression | FunctionDeclaration} node
@ -16,6 +17,9 @@ function remove_this_param(node, context) {
/** @type {Visitors<any, null>} */
const visitors = {
Decorator(node) {
e.typescript_invalid_feature(node, 'decorators (related TSC proposal is not stage 4 yet)');
},
ImportDeclaration(node) {
if (node.importKind === 'type') return b.empty;
@ -52,6 +56,14 @@ const visitors = {
if (node.exportKind === 'type') return b.empty;
return node;
},
PropertyDefinition(node) {
if (node.accessor) {
e.typescript_invalid_feature(
node,
'accessor fields (related TSC proposal is not stage 4 yet)'
);
}
},
TSAsExpression(node, context) {
return context.visit(node.expression);
},
@ -61,6 +73,11 @@ const visitors = {
TSNonNullExpression(node, context) {
return context.visit(node.expression);
},
TSTypeAnnotation() {
// This isn't correct, strictly speaking, and could result in invalid ASTs (like an empty statement within function parameters),
// but esrap, our printing tool, just ignores these AST nodes at invalid positions, so it's fine
return b.empty;
},
TSInterfaceDeclaration() {
return b.empty;
},
@ -73,23 +90,43 @@ const visitors = {
TSTypeParameterInstantiation() {
return b.empty;
},
TSEnumDeclaration() {
return b.empty;
TSEnumDeclaration(node) {
e.typescript_invalid_feature(node, 'enums');
},
TSParameterProperty(node) {
TSParameterProperty(node, context) {
if (node.accessibility && context.path.at(-2)?.kind === 'constructor') {
e.typescript_invalid_feature(node, 'accessibility modifiers on constructor parameters');
}
return node.parameter;
},
Identifier(node) {
if (node.typeAnnotation) {
return {
...node,
typeAnnotation: null
};
FunctionExpression: remove_this_param,
FunctionDeclaration: remove_this_param,
TSDeclareFunction() {
return b.empty;
},
ClassDeclaration(node, context) {
if (node.declare) {
return b.empty;
}
return node;
return context.next();
},
FunctionExpression: remove_this_param,
FunctionDeclaration: remove_this_param
VariableDeclaration(node, context) {
if (node.declare) {
return b.empty;
}
return context.next();
},
TSModuleDeclaration(node, context) {
if (!node.body) return b.empty;
// namespaces can contain non-type nodes
const cleaned = /** @type {any[]} */ (node.body.body).map((entry) => context.visit(entry));
if (cleaned.some((entry) => entry !== b.empty)) {
e.typescript_invalid_feature(node, 'namespaces with non-type nodes');
}
return b.empty;
}
};
/**

@ -1,5 +1,5 @@
/** @import { Expression } from 'estree' */
/** @import * as Compiler from '#compiler' */
/** @import { AST, Directive, ElementLike, TemplateNode } from '#compiler' */
/** @import { Parser } from '../index.js' */
import { is_void } from '../../../../utils.js';
import read_expression from '../read/expression.js';
@ -12,14 +12,23 @@ import { create_fragment } from '../utils/create.js';
import { create_attribute, create_expression_metadata } from '../../nodes.js';
import { get_attribute_expression, is_expression_attribute } from '../../../utils/ast.js';
import { closing_tag_omitted } from '../../../../html-tree-validation.js';
import { list } from '../../../utils/string.js';
// eslint-disable-next-line no-useless-escape
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
/** Invalid attribute characters if the attribute is not surrounded by quotes */
const regex_starts_with_invalid_attr_value = /^(\/>|[\s"'=<>`])/;
const regex_invalid_unquoted_attribute_value = /^(\/>|[\s"'=<>`])/;
const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i;
const regex_closing_comment = /-->/;
const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/;
const regex_token_ending_character = /[\s=/>"']/;
const regex_starts_with_quote_characters = /^["']/;
const regex_attribute_value = /^(?:"([^"]*)"|'([^'])*'|([^>\s]+))/;
const regex_valid_element_name =
/^(?:![a-zA-Z]+|[a-zA-Z](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|[a-zA-Z][a-zA-Z0-9]*:[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])$/;
const regex_valid_component_name =
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers adjusted for our needs
// (must start with uppercase letter if no dots, can contain dots)
/^(?:\p{Lu}[$\u200c\u200d\p{ID_Continue}.]*|\p{ID_Start}[$\u200c\u200d\p{ID_Continue}]*(?:\.[$\u200c\u200d\p{ID_Continue}]+)+)$/u;
/** @type {Map<string, Compiler.ElementLike['type']>} */
/** @type {Map<string, ElementLike['type']>} */
const root_only_meta_tags = new Map([
['svelte:head', 'SvelteHead'],
['svelte:options', 'SvelteOptions'],
@ -28,7 +37,7 @@ const root_only_meta_tags = new Map([
['svelte:body', 'SvelteBody']
]);
/** @type {Map<string, Compiler.ElementLike['type']>} */
/** @type {Map<string, ElementLike['type']>} */
const meta_tags = new Map([
...root_only_meta_tags,
['svelte:element', 'SvelteElement'],
@ -37,47 +46,6 @@ const meta_tags = new Map([
['svelte:fragment', 'SvelteFragment']
]);
const valid_meta_tags = Array.from(meta_tags.keys());
const SELF = /^svelte:self(?=[\s/>])/;
const COMPONENT = /^svelte:component(?=[\s/>])/;
const SLOT = /^svelte:fragment(?=[\s/>])/;
const ELEMENT = /^svelte:element(?=[\s/>])/;
/** @param {Compiler.TemplateNode[]} stack */
function parent_is_head(stack) {
let i = stack.length;
while (i--) {
const { type } = stack[i];
if (type === 'SvelteHead') return true;
if (type === 'RegularElement' || type === 'Component') return false;
}
return false;
}
/** @param {Compiler.TemplateNode[]} stack */
function parent_is_shadowroot_template(stack) {
// https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#building_a_declarative_shadow_root
let i = stack.length;
while (i--) {
if (
stack[i].type === 'RegularElement' &&
/** @type {Compiler.RegularElement} */ (stack[i]).attributes.some(
(a) => a.type === 'Attribute' && a.name === 'shadowrootmode'
)
) {
return true;
}
}
return false;
}
const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i;
const regex_closing_comment = /-->/;
const regex_component_name = /^(?:[A-Z]|[A-Za-z][A-Za-z0-9_$]*\.)/;
const regex_valid_component_name =
/^(?:[A-Z][A-Za-z0-9_$.]*|[a-z][A-Za-z0-9_$]*\.[A-Za-z0-9_$])[A-Za-z0-9_$.]*$/;
/** @param {Parser} parser */
export default function element(parser) {
const start = parser.index++;
@ -88,7 +56,7 @@ export default function element(parser) {
const data = parser.read_until(regex_closing_comment);
parser.eat('-->', true);
/** @type {ReturnType<typeof parser.append<Compiler.Comment>>} */
/** @type {ReturnType<typeof parser.append<AST.Comment>>} */
parser.append({
type: 'Comment',
start,
@ -100,36 +68,67 @@ export default function element(parser) {
}
const is_closing_tag = parser.eat('/');
const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag);
const name = read_tag_name(parser);
if (is_closing_tag) {
parser.allow_whitespace();
parser.eat('>', true);
if (root_only_meta_tags.has(name)) {
if (is_closing_tag) {
if (
['svelte:options', 'svelte:window', 'svelte:body', 'svelte:document'].includes(name) &&
/** @type {Compiler.ElementLike} */ (parent).fragment.nodes.length
) {
e.svelte_meta_invalid_content(
/** @type {Compiler.ElementLike} */ (parent).fragment.nodes[0].start,
name
);
}
} else {
if (name in parser.meta_tags) {
e.svelte_meta_duplicate(start, name);
}
if (is_void(name)) {
e.void_element_invalid_content(start);
}
if (parent.type !== 'Root') {
e.svelte_meta_invalid_placement(start, name);
// close any elements that don't have their own closing tags, e.g. <div><p></div>
while (/** @type {AST.RegularElement} */ (parent).name !== name) {
if (parent.type !== 'RegularElement') {
if (parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name) {
e.element_invalid_closing_tag_autoclosed(start, name, parser.last_auto_closed_tag.reason);
} else {
e.element_invalid_closing_tag(start, name);
}
}
parser.meta_tags[name] = true;
parent.end = start;
parser.pop();
parent = parser.current();
}
parent.end = parser.index;
parser.pop();
if (parser.last_auto_closed_tag && parser.stack.length < parser.last_auto_closed_tag.depth) {
parser.last_auto_closed_tag = undefined;
}
return;
}
if (name.startsWith('svelte:') && !meta_tags.has(name)) {
const bounds = { start: start + 1, end: start + 1 + name.length };
e.svelte_meta_invalid_tag(bounds, list(Array.from(meta_tags.keys())));
}
if (!regex_valid_element_name.test(name) && !regex_valid_component_name.test(name)) {
const bounds = { start: start + 1, end: start + 1 + name.length };
e.tag_invalid_name(bounds);
}
if (root_only_meta_tags.has(name)) {
if (name in parser.meta_tags) {
e.svelte_meta_duplicate(start, name);
}
if (parent.type !== 'Root') {
e.svelte_meta_invalid_placement(start, name);
}
parser.meta_tags[name] = true;
}
const type = meta_tags.has(name)
? meta_tags.get(name)
: regex_component_name.test(name)
: regex_valid_component_name.test(name)
? 'Component'
: name === 'title' && parent_is_head(parser.stack)
? 'TitleElement'
@ -138,11 +137,7 @@ export default function element(parser) {
? 'SlotElement'
: 'RegularElement';
if (type === 'Component' && !regex_valid_component_name.test(name)) {
e.component_invalid_name({ start: start + 1, end: start + name.length + 1 });
}
/** @type {Compiler.ElementLike} */
/** @type {ElementLike} */
const element =
type === 'RegularElement'
? {
@ -160,7 +155,7 @@ export default function element(parser) {
},
parent: null
}
: /** @type {Compiler.ElementLike} */ ({
: /** @type {ElementLike} */ ({
type,
start,
end: -1,
@ -175,38 +170,7 @@ export default function element(parser) {
parser.allow_whitespace();
if (is_closing_tag) {
if (is_void(name)) {
e.void_element_invalid_content(start);
}
parser.eat('>', true);
// close any elements that don't have their own closing tags, e.g. <div><p></div>
while (/** @type {Compiler.RegularElement} */ (parent).name !== name) {
if (parent.type !== 'RegularElement') {
if (parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name) {
e.element_invalid_closing_tag_autoclosed(start, name, parser.last_auto_closed_tag.reason);
} else {
e.element_invalid_closing_tag(start, name);
}
}
parent.end = start;
parser.pop();
parent = parser.current();
}
parent.end = parser.index;
parser.pop();
if (parser.last_auto_closed_tag && parser.stack.length < parser.last_auto_closed_tag.depth) {
parser.last_auto_closed_tag = undefined;
}
return;
} else if (parent.type === 'RegularElement' && closing_tag_omitted(parent.name, name)) {
if (parent.type === 'RegularElement' && closing_tag_omitted(parent.name, name)) {
parent.end = start;
parser.pop();
parser.last_auto_closed_tag = {
@ -227,12 +191,24 @@ export default function element(parser) {
let attribute;
while ((attribute = read(parser))) {
if (attribute.type === 'Attribute' || attribute.type === 'BindDirective') {
if (unique_names.includes(attribute.name)) {
// animate and transition can only be specified once per element so no need
// to check here, use can be used multiple times, same for the on directive
// finally let already has error handling in case of duplicate variable names
if (
attribute.type === 'Attribute' ||
attribute.type === 'BindDirective' ||
attribute.type === 'StyleDirective' ||
attribute.type === 'ClassDirective'
) {
// `bind:attribute` and `attribute` are just the same but `class:attribute`,
// `style:attribute` and `attribute` are different and should be allowed together
// so we concatenate the type while normalizing the type for BindDirective
const type = attribute.type === 'BindDirective' ? 'Attribute' : attribute.type;
if (unique_names.includes(type + attribute.name)) {
e.attribute_duplicate(attribute);
// <svelte:element bind:this this=..> is allowed
} else if (attribute.name !== 'this') {
unique_names.push(attribute.name);
unique_names.push(type + attribute.name);
}
}
@ -249,7 +225,7 @@ export default function element(parser) {
e.svelte_component_missing_this(start);
}
const definition = /** @type {Compiler.Attribute} */ (element.attributes.splice(index, 1)[0]);
const definition = /** @type {AST.Attribute} */ (element.attributes.splice(index, 1)[0]);
if (!is_expression_attribute(definition)) {
e.svelte_component_invalid_this(definition.start);
}
@ -266,7 +242,7 @@ export default function element(parser) {
e.svelte_element_missing_this(start);
}
const definition = /** @type {Compiler.Attribute} */ (element.attributes.splice(index, 1)[0]);
const definition = /** @type {AST.Attribute} */ (element.attributes.splice(index, 1)[0]);
if (definition.value === true) {
e.svelte_element_missing_this(definition);
@ -279,9 +255,7 @@ export default function element(parser) {
// it would be much better to just error here, but we are preserving the existing buggy
// Svelte 4 behaviour out of an overabundance of caution regarding breaking changes.
// TODO in 6.0, error
const chunk = /** @type {Array<Compiler.ExpressionTag | Compiler.Text>} */ (
definition.value
)[0];
const chunk = /** @type {Array<AST.ExpressionTag | AST.Text>} */ (definition.value)[0];
element.tag =
chunk.type === 'Text'
? {
@ -300,7 +274,7 @@ export default function element(parser) {
if (is_top_level_script_or_style) {
parser.eat('>', true);
/** @type {Compiler.Comment | null} */
/** @type {AST.Comment | null} */
let prev_comment = null;
for (let i = current.fragment.nodes.length - 1; i >= 0; i--) {
const node = current.fragment.nodes[i];
@ -367,7 +341,7 @@ export default function element(parser) {
const data = parser.read_until(new RegExp(`</${name}>`));
const end = parser.index;
/** @type {Compiler.Text} */
/** @type {AST.Text} */
const node = {
start,
end,
@ -386,67 +360,37 @@ export default function element(parser) {
}
}
const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/;
/** @param {Parser} parser */
function read_tag_name(parser) {
const start = parser.index;
if (parser.read(SELF)) {
// check we're inside a block, otherwise this
// will cause infinite recursion
let i = parser.stack.length;
let legal = false;
while (i--) {
const fragment = parser.stack[i];
if (
fragment.type === 'IfBlock' ||
fragment.type === 'EachBlock' ||
fragment.type === 'Component' ||
fragment.type === 'SnippetBlock'
) {
legal = true;
break;
}
}
if (!legal) {
e.svelte_self_invalid_placement(start);
}
return 'svelte:self';
}
if (parser.read(COMPONENT)) return 'svelte:component';
if (parser.read(ELEMENT)) return 'svelte:element';
if (parser.read(SLOT)) return 'svelte:fragment';
const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag);
if (meta_tags.has(name)) return name;
if (name.startsWith('svelte:')) {
const list = `${valid_meta_tags.slice(0, -1).join(', ')} or ${valid_meta_tags[valid_meta_tags.length - 1]}`;
e.svelte_meta_invalid_tag(start, list);
/** @param {TemplateNode[]} stack */
function parent_is_head(stack) {
let i = stack.length;
while (i--) {
const { type } = stack[i];
if (type === 'SvelteHead') return true;
if (type === 'RegularElement' || type === 'Component') return false;
}
return false;
}
if (!valid_tag_name.test(name)) {
e.element_invalid_tag_name(start);
/** @param {TemplateNode[]} stack */
function parent_is_shadowroot_template(stack) {
// https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#building_a_declarative_shadow_root
let i = stack.length;
while (i--) {
if (
stack[i].type === 'RegularElement' &&
/** @type {AST.RegularElement} */ (stack[i]).attributes.some(
(a) => a.type === 'Attribute' && a.name === 'shadowrootmode'
)
) {
return true;
}
}
return name;
return false;
}
// eslint-disable-next-line no-useless-escape
const regex_token_ending_character = /[\s=\/>"']/;
const regex_starts_with_quote_characters = /^["']/;
const regex_attribute_value = /^(?:"([^"]*)"|'([^'])*'|([^>\s]+))/;
/**
* @param {Parser} parser
* @returns {Compiler.Attribute | null}
* @returns {AST.Attribute | null}
*/
function read_static_attribute(parser) {
const start = parser.index;
@ -454,7 +398,7 @@ function read_static_attribute(parser) {
const name = parser.read_until(regex_token_ending_character);
if (!name) return null;
/** @type {true | Array<Compiler.Text | Compiler.ExpressionTag>} */
/** @type {true | Array<AST.Text | AST.ExpressionTag>} */
let value = true;
if (parser.eat('=')) {
@ -492,7 +436,7 @@ function read_static_attribute(parser) {
/**
* @param {Parser} parser
* @returns {Compiler.Attribute | Compiler.SpreadAttribute | Compiler.Directive | null}
* @returns {AST.Attribute | AST.SpreadAttribute | Directive | null}
*/
function read_attribute(parser) {
const start = parser.index;
@ -506,7 +450,7 @@ function read_attribute(parser) {
parser.allow_whitespace();
parser.eat('}', true);
/** @type {Compiler.SpreadAttribute} */
/** @type {AST.SpreadAttribute} */
const spread = {
type: 'SpreadAttribute',
start,
@ -530,7 +474,7 @@ function read_attribute(parser) {
parser.allow_whitespace();
parser.eat('}', true);
/** @type {Compiler.ExpressionTag} */
/** @type {AST.ExpressionTag} */
const expression = {
type: 'ExpressionTag',
start: value_start,
@ -561,7 +505,7 @@ function read_attribute(parser) {
const colon_index = name.indexOf(':');
const type = colon_index !== -1 && get_directive_type(name.slice(0, colon_index));
/** @type {true | Compiler.ExpressionTag | Array<Compiler.Text | Compiler.ExpressionTag>} */
/** @type {true | AST.ExpressionTag | Array<AST.Text | AST.ExpressionTag>} */
let value = true;
if (parser.eat('=')) {
parser.allow_whitespace();
@ -610,7 +554,7 @@ function read_attribute(parser) {
}
}
/** @type {Compiler.Directive} */
/** @type {Directive} */
// @ts-expect-error TODO can't figure out this error
const directive = {
start,
@ -667,7 +611,7 @@ function get_directive_type(name) {
/**
* @param {Parser} parser
* @return {Compiler.ExpressionTag | Array<Compiler.ExpressionTag | Compiler.Text>}
* @return {AST.ExpressionTag | Array<AST.ExpressionTag | AST.Text>}
*/
function read_attribute_value(parser) {
const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null;
@ -684,7 +628,7 @@ function read_attribute_value(parser) {
];
}
/** @type {Array<Compiler.ExpressionTag | Compiler.Text>} */
/** @type {Array<AST.ExpressionTag | AST.Text>} */
let value;
try {
value = read_sequence(
@ -692,7 +636,7 @@ function read_attribute_value(parser) {
() => {
// handle common case of quote marks existing outside of regex for performance reasons
if (quote_mark) return parser.match(quote_mark);
return !!parser.match_regex(regex_starts_with_invalid_attr_value);
return !!parser.match_regex(regex_invalid_unquoted_attribute_value);
},
'in attribute value'
);
@ -730,7 +674,7 @@ function read_attribute_value(parser) {
* @returns {any[]}
*/
function read_sequence(parser, done, location) {
/** @type {Compiler.Text} */
/** @type {AST.Text} */
let current_chunk = {
start: parser.index,
end: -1,
@ -740,7 +684,7 @@ function read_sequence(parser, done, location) {
parent: null
};
/** @type {Array<Compiler.Text | Compiler.ExpressionTag>} */
/** @type {Array<AST.Text | AST.ExpressionTag>} */
const chunks = [];
/** @param {number} end */
@ -778,7 +722,7 @@ function read_sequence(parser, done, location) {
parser.allow_whitespace();
parser.eat('}', true);
/** @type {Compiler.ExpressionTag} */
/** @type {AST.ExpressionTag} */
const chunk = {
type: 'ExpressionTag',
start: index,

@ -1,5 +1,5 @@
/** @import { ArrowFunctionExpression, Expression, Identifier } from 'estree' */
/** @import { AwaitBlock, ConstTag, DebugTag, EachBlock, ExpressionTag, HtmlTag, IfBlock, KeyBlock, RenderTag, SnippetBlock } from '#compiler' */
/** @import { AST } from '#compiler' */
/** @import { Parser } from '../index.js' */
import read_pattern from '../read/context.js';
import read_expression from '../read/expression.js';
@ -33,7 +33,7 @@ export default function tag(parser) {
parser.allow_whitespace();
parser.eat('}', true);
/** @type {ReturnType<typeof parser.append<ExpressionTag>>} */
/** @type {ReturnType<typeof parser.append<AST.ExpressionTag>>} */
parser.append({
type: 'ExpressionTag',
start,
@ -53,7 +53,7 @@ function open(parser) {
if (parser.eat('if')) {
parser.require_whitespace();
/** @type {ReturnType<typeof parser.append<IfBlock>>} */
/** @type {ReturnType<typeof parser.append<AST.IfBlock>>} */
const block = parser.append({
type: 'IfBlock',
elseif: false,
@ -174,7 +174,7 @@ function open(parser) {
parser.eat('}', true);
/** @type {ReturnType<typeof parser.append<EachBlock>>} */
/** @type {ReturnType<typeof parser.append<AST.EachBlock>>} */
const block = parser.append({
type: 'EachBlock',
start,
@ -198,7 +198,7 @@ function open(parser) {
const expression = read_expression(parser);
parser.allow_whitespace();
/** @type {ReturnType<typeof parser.append<AwaitBlock>>} */
/** @type {ReturnType<typeof parser.append<AST.AwaitBlock>>} */
const block = parser.append({
type: 'AwaitBlock',
start,
@ -252,7 +252,7 @@ function open(parser) {
parser.eat('}', true);
/** @type {ReturnType<typeof parser.append<KeyBlock>>} */
/** @type {ReturnType<typeof parser.append<AST.KeyBlock>>} */
const block = parser.append({
type: 'KeyBlock',
start,
@ -303,7 +303,7 @@ function open(parser) {
parser.allow_whitespace();
parser.eat('}', true);
/** @type {ReturnType<typeof parser.append<SnippetBlock>>} */
/** @type {ReturnType<typeof parser.append<AST.SnippetBlock>>} */
const block = parser.append({
type: 'SnippetBlock',
start,
@ -355,7 +355,7 @@ function next(parser) {
let elseif_start = start - 1;
while (parser.template[elseif_start] !== '{') elseif_start -= 1;
/** @type {ReturnType<typeof parser.append<IfBlock>>} */
/** @type {ReturnType<typeof parser.append<AST.IfBlock>>} */
const child = parser.append({
start: elseif_start,
end: -1,
@ -451,7 +451,7 @@ function close(parser) {
while (block.elseif) {
block.end = parser.index;
parser.stack.pop();
block = /** @type {IfBlock} */ (parser.current());
block = /** @type {AST.IfBlock} */ (parser.current());
}
block.end = parser.index;
parser.pop();
@ -499,7 +499,7 @@ function special(parser) {
parser.allow_whitespace();
parser.eat('}', true);
/** @type {ReturnType<typeof parser.append<HtmlTag>>} */
/** @type {ReturnType<typeof parser.append<AST.HtmlTag>>} */
parser.append({
type: 'HtmlTag',
start,
@ -537,7 +537,7 @@ function special(parser) {
parser.eat('}', true);
}
/** @type {ReturnType<typeof parser.append<DebugTag>>} */
/** @type {ReturnType<typeof parser.append<AST.DebugTag>>} */
parser.append({
type: 'DebugTag',
start,
@ -570,7 +570,7 @@ function special(parser) {
parser.eat('}', true);
/** @type {ReturnType<typeof parser.append<ConstTag>>} */
/** @type {ReturnType<typeof parser.append<AST.ConstTag>>} */
parser.append({
type: 'ConstTag',
start,
@ -601,7 +601,7 @@ function special(parser) {
parser.allow_whitespace();
parser.eat('}', true);
/** @type {ReturnType<typeof parser.append<RenderTag>>} */
/** @type {ReturnType<typeof parser.append<AST.RenderTag>>} */
parser.append({
type: 'RenderTag',
start,

@ -1,4 +1,4 @@
/** @import { Text } from '#compiler' */
/** @import { AST } from '#compiler' */
/** @import { Parser } from '../index.js' */
import { decode_character_references } from '../utils/html.js';
@ -12,7 +12,7 @@ export default function text(parser) {
data += parser.template[parser.index++];
}
/** @type {ReturnType<typeof parser.append<Text>>} */
/** @type {ReturnType<typeof parser.append<AST.Text>>} */
parser.append({
type: 'Text',
start,

@ -1,7 +1,8 @@
/** @import { Fragment } from '#compiler' */
/** @import { AST } from '#compiler' */
/**
* @param {any} transparent
* @returns {Fragment}
* @returns {AST.Fragment}
*/
export function create_fragment(transparent = false) {
return {

@ -222,7 +222,18 @@ const css_visitors = {
const parent_rule = rule.metadata.parent_rule;
if (!parent_rule) {
e.css_nesting_selector_invalid_placement(node);
// https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#using_outside_nested_rule
const children = rule.prelude.children;
const selectors = children[0].children[0].selectors;
if (
children.length > 1 ||
selectors.length > 1 ||
selectors[0].type !== 'PseudoClassSelector' ||
selectors[0].name !== 'global' ||
selectors[0].args?.children[0]?.children[0].selectors[0] !== node
) {
e.css_nesting_selector_invalid_placement(node);
}
} else if (
// :global { &.foo { ... } } is invalid
parent_rule.metadata.is_global_block &&

@ -8,7 +8,7 @@ import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js';
/**
* @typedef {{
* stylesheet: Compiler.Css.StyleSheet;
* element: Compiler.RegularElement | Compiler.SvelteElement;
* element: Compiler.AST.RegularElement | Compiler.AST.SvelteElement;
* }} State
*/
/** @typedef {NODE_PROBABLY_EXISTS | NODE_DEFINITELY_EXISTS} NodeExistsValue */
@ -53,7 +53,7 @@ const nesting_selector = {
/**
*
* @param {Compiler.Css.StyleSheet} stylesheet
* @param {Compiler.RegularElement | Compiler.SvelteElement} element
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
*/
export function prune(stylesheet, element) {
walk(stylesheet, { stylesheet, element }, visitors);
@ -73,9 +73,23 @@ const visitors = {
const inner = selectors[selectors.length - 1];
if (node.metadata.rule?.metadata.parent_rule && selectors.length > 0) {
const has_explicit_nesting_selector = selectors.some((selector) =>
selector.selectors.some((s) => s.type === 'NestingSelector')
);
let has_explicit_nesting_selector = false;
// nesting could be inside pseudo classes like :is, :has or :where
for (let selector of selectors) {
walk(
selector,
{},
{
// @ts-ignore
NestingSelector() {
has_explicit_nesting_selector = true;
}
}
);
// if we found one we can break from the others
if (has_explicit_nesting_selector) break;
}
if (!has_explicit_nesting_selector) {
selectors[0] = {
@ -120,7 +134,7 @@ function truncate(node) {
/**
* @param {Compiler.Css.RelativeSelector[]} relative_selectors
* @param {Compiler.Css.Rule} rule
* @param {Compiler.RegularElement | Compiler.SvelteElement} element
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
* @param {Compiler.Css.StyleSheet} stylesheet
* @returns {boolean}
*/
@ -222,7 +236,7 @@ function apply_selector(relative_selectors, rule, element, stylesheet) {
* Mark both the compound selector and the node it selects as encapsulated,
* for transformation in a later step
* @param {Compiler.Css.RelativeSelector} relative_selector
* @param {Compiler.RegularElement | Compiler.SvelteElement} element
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
*/
function mark(relative_selector, element) {
relative_selector.metadata.scoped = true;
@ -279,7 +293,7 @@ const regex_backslash_and_following_character = /\\(.)/g;
*
* @param {Compiler.Css.RelativeSelector} relative_selector
* @param {Compiler.Css.Rule} rule
* @param {Compiler.RegularElement | Compiler.SvelteElement} element
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element
* @param {Compiler.Css.StyleSheet} stylesheet
* @returns {boolean}
*/
@ -435,7 +449,7 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
}
/**
* @param {Compiler.RegularElement | Compiler.SvelteElement} node
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} node
* @param {string} name
* @param {string | null} expected_value
* @param {string | null} operator
@ -542,8 +556,8 @@ function unquote(str) {
}
/**
* @param {Compiler.RegularElement | Compiler.SvelteElement} node
* @returns {Compiler.RegularElement | Compiler.SvelteElement | null}
* @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} node
* @returns {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | null}
*/
function get_element_parent(node) {
/** @type {Compiler.SvelteNode | null} */
@ -611,10 +625,10 @@ function find_previous_sibling(node) {
/**
* @param {Compiler.SvelteNode} node
* @param {boolean} adjacent_only
* @returns {Map<Compiler.RegularElement | Compiler.SvelteElement | Compiler.SlotElement | Compiler.RenderTag, NodeExistsValue>}
* @returns {Map<Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.SlotElement | Compiler.AST.RenderTag, NodeExistsValue>}
*/
function get_possible_element_siblings(node, adjacent_only) {
/** @type {Map<Compiler.RegularElement | Compiler.SvelteElement | Compiler.SlotElement | Compiler.RenderTag, NodeExistsValue>} */
/** @type {Map<Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.SlotElement | Compiler.AST.RenderTag, NodeExistsValue>} */
const result = new Map();
/** @type {Compiler.SvelteNode} */
@ -676,12 +690,12 @@ function get_possible_element_siblings(node, adjacent_only) {
}
/**
* @param {Compiler.EachBlock | Compiler.IfBlock | Compiler.AwaitBlock} relative_selector
* @param {Compiler.AST.EachBlock | Compiler.AST.IfBlock | Compiler.AST.AwaitBlock} relative_selector
* @param {boolean} adjacent_only
* @returns {Map<Compiler.RegularElement, NodeExistsValue>}
* @returns {Map<Compiler.AST.RegularElement, NodeExistsValue>}
*/
function get_possible_last_child(relative_selector, adjacent_only) {
/** @typedef {Map<Compiler.RegularElement, NodeExistsValue>} NodeMap */
/** @typedef {Map<Compiler.AST.RegularElement, NodeExistsValue>} NodeMap */
/** @type {NodeMap} */
const result = new Map();
@ -783,7 +797,7 @@ function higher_existence(exist1, exist2) {
return exist1 > exist2 ? exist1 : exist2;
}
/** @param {Map<Compiler.RegularElement, NodeExistsValue>} result */
/** @param {Map<Compiler.AST.RegularElement, NodeExistsValue>} result */
function mark_as_probably(result) {
for (const key of result.keys()) {
result.set(key, NODE_PROBABLY_EXISTS);
@ -795,7 +809,7 @@ function mark_as_probably(result) {
* @param {boolean} adjacent_only
*/
function loop_child(children, adjacent_only) {
/** @type {Map<Compiler.RegularElement, NodeExistsValue>} */
/** @type {Map<Compiler.AST.RegularElement, NodeExistsValue>} */
const result = new Map();
for (let i = children.length - 1; i >= 0; i--) {
const child = children[i];

@ -1,4 +1,4 @@
/** @import { Text, ExpressionTag } from '#compiler' */
/** @import { AST } from '#compiler' */
/** @import { Node } from 'estree' */
const UNKNOWN = {};
@ -18,7 +18,7 @@ function gather_possible_values(node, set) {
}
/**
* @param {Text | ExpressionTag} chunk
* @param {AST.Text | AST.ExpressionTag} chunk
* @returns {Set<string> | null}
*/
export function get_possible_values(chunk) {

@ -1,5 +1,5 @@
/** @import { Expression, Node, Program } from 'estree' */
/** @import { Binding, Root, Script, SvelteNode, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
/** @import { Binding, AST, SvelteNode, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */
/** @import { AnalysisState, Visitors } from './types' */
/** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */
import { walk } from 'zimmerframe';
@ -52,11 +52,14 @@ import { SlotElement } from './visitors/SlotElement.js';
import { SnippetBlock } from './visitors/SnippetBlock.js';
import { SpreadAttribute } from './visitors/SpreadAttribute.js';
import { StyleDirective } from './visitors/StyleDirective.js';
import { SvelteBody } from './visitors/SvelteBody.js';
import { SvelteComponent } from './visitors/SvelteComponent.js';
import { SvelteDocument } from './visitors/SvelteDocument.js';
import { SvelteElement } from './visitors/SvelteElement.js';
import { SvelteFragment } from './visitors/SvelteFragment.js';
import { SvelteHead } from './visitors/SvelteHead.js';
import { SvelteSelf } from './visitors/SvelteSelf.js';
import { SvelteWindow } from './visitors/SvelteWindow.js';
import { TaggedTemplateExpression } from './visitors/TaggedTemplateExpression.js';
import { Text } from './visitors/Text.js';
import { TitleElement } from './visitors/TitleElement.js';
@ -158,11 +161,14 @@ const visitors = {
SnippetBlock,
SpreadAttribute,
StyleDirective,
SvelteHead,
SvelteBody,
SvelteComponent,
SvelteDocument,
SvelteElement,
SvelteFragment,
SvelteComponent,
SvelteHead,
SvelteSelf,
SvelteWindow,
TaggedTemplateExpression,
Text,
TitleElement,
@ -172,7 +178,7 @@ const visitors = {
};
/**
* @param {Script | null} script
* @param {AST.Script | null} script
* @param {ScopeRoot} root
* @param {boolean} allow_reactive_declarations
* @param {Scope | null} parent
@ -251,7 +257,7 @@ export function analyze_module(ast, options) {
}
/**
* @param {Root} root
* @param {AST.Root} root
* @param {string} source
* @param {ValidatedCompileOptions} options
* @returns {ComponentAnalysis}
@ -574,7 +580,7 @@ export function analyze_component(root, source, options) {
// bind:this doesn't need to be a state reference if it will never change
if (
type === 'BindDirective' &&
/** @type {import('#compiler').BindDirective} */ (path[i]).name === 'this'
/** @type {AST.BindDirective} */ (path[i]).name === 'this'
) {
for (let j = i - 1; j >= 0; j -= 1) {
const type = path[j].type;
@ -682,7 +688,7 @@ export function analyze_component(root, source, options) {
// TODO this happens during the analysis phase, which shouldn't know anything about client vs server
if (element.type === 'SvelteElement' && options.generate === 'client') continue;
/** @type {import('#compiler').Attribute | undefined} */
/** @type {AST.Attribute | undefined} */
let class_attribute = undefined;
for (const attribute of element.attributes) {
@ -701,7 +707,7 @@ export function analyze_component(root, source, options) {
if (is_text_attribute(class_attribute)) {
class_attribute.value[0].data += ` ${analysis.css.hash}`;
} else {
/** @type {import('#compiler').Text} */
/** @type {AST.Text} */
const css_text = {
type: 'Text',
data: ` ${analysis.css.hash}`,

@ -1,6 +1,6 @@
import type { Scope } from '../scope.js';
import type { ComponentAnalysis, ReactiveStatement } from '../types.js';
import type { ExpressionMetadata, RenderTag, SvelteNode, ValidatedCompileOptions } from '#compiler';
import type { ExpressionMetadata, AST, ValidatedCompileOptions, SvelteNode } from '#compiler';
import type { LabeledStatement } from 'estree';
export interface AnalysisState {
@ -16,7 +16,7 @@ export interface AnalysisState {
/** Information about the current expression/directive/block value */
expression: ExpressionMetadata | null;
/** The current {@render ...} tag, if any */
render_tag: null | RenderTag;
render_tag: null | AST.RenderTag;
private_derived_state: string[];
function_depth: number;

@ -1,5 +1,5 @@
/** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression } from 'estree' */
/** @import { Attribute, DelegatedEvent, RegularElement, SvelteNode } from '#compiler' */
/** @import { AST, DelegatedEvent, SvelteNode } from '#compiler' */
/** @import { Context } from '../types' */
import { is_capture_event, is_delegated } from '../../../../utils.js';
import {
@ -10,7 +10,7 @@ import {
import { mark_subtree_dynamic } from './shared/fragment.js';
/**
* @param {Attribute} node
* @param {AST.Attribute} node
* @param {Context} context
*/
export function Attribute(node, context) {
@ -107,20 +107,20 @@ function get_delegated_event(event_name, handler, context) {
const grandparent = path.at(-2);
/** @type {RegularElement | null} */
/** @type {AST.RegularElement | null} */
let element = null;
/** @type {string | null} */
let event_name = null;
if (parent.type === 'OnDirective') {
element = /** @type {RegularElement} */ (grandparent);
element = /** @type {AST.RegularElement} */ (grandparent);
event_name = parent.name;
} else if (
parent.type === 'ExpressionTag' &&
grandparent?.type === 'Attribute' &&
is_event_attribute(grandparent)
) {
element = /** @type {RegularElement} */ (path.at(-3));
const attribute = /** @type {Attribute} */ (grandparent);
element = /** @type {AST.RegularElement} */ (path.at(-3));
const attribute = /** @type {AST.Attribute} */ (grandparent);
event_name = get_attribute_event_name(attribute.name);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save