diff --git a/.changeset/chatty-taxis-juggle.md b/.changeset/chatty-taxis-juggle.md new file mode 100644 index 000000000..34eb8bc9a --- /dev/null +++ b/.changeset/chatty-taxis-juggle.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: delegate events on elements with bind-this diff --git a/.changeset/chilled-pumas-invite.md b/.changeset/chilled-pumas-invite.md new file mode 100644 index 000000000..943f2d483 --- /dev/null +++ b/.changeset/chilled-pumas-invite.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: apply keyed validation only for keyed each diff --git a/.changeset/cold-birds-own.md b/.changeset/cold-birds-own.md new file mode 100644 index 000000000..043f64b02 --- /dev/null +++ b/.changeset/cold-birds-own.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: add inline new class warning diff --git a/.changeset/dirty-garlics-design.md b/.changeset/dirty-garlics-design.md new file mode 100644 index 000000000..525160b76 --- /dev/null +++ b/.changeset/dirty-garlics-design.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: add type of `$effect.active` diff --git a/.changeset/dirty-tips-add.md b/.changeset/dirty-tips-add.md new file mode 100644 index 000000000..305dbd1b1 --- /dev/null +++ b/.changeset/dirty-tips-add.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correct bind this multiple bindings diff --git a/.changeset/fair-crabs-check.md b/.changeset/fair-crabs-check.md new file mode 100644 index 000000000..fa51ebf2d --- /dev/null +++ b/.changeset/fair-crabs-check.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent false positives when detecting runes mode diff --git a/.changeset/famous-knives-sneeze.md b/.changeset/famous-knives-sneeze.md new file mode 100644 index 000000000..9c7dcb42d --- /dev/null +++ b/.changeset/famous-knives-sneeze.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure implicit children snippet renders correctly diff --git a/.changeset/few-mugs-fail.md b/.changeset/few-mugs-fail.md new file mode 100644 index 000000000..b3a4a611e --- /dev/null +++ b/.changeset/few-mugs-fail.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: reuse common templates diff --git a/.changeset/flat-melons-protect.md b/.changeset/flat-melons-protect.md new file mode 100644 index 000000000..43a8c4575 --- /dev/null +++ b/.changeset/flat-melons-protect.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure `$$slots` exists in runes mode diff --git a/.changeset/forty-comics-invent.md b/.changeset/forty-comics-invent.md new file mode 100644 index 000000000..e507f3621 --- /dev/null +++ b/.changeset/forty-comics-invent.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: handle undefined bubble events diff --git a/.changeset/friendly-lies-camp.md b/.changeset/friendly-lies-camp.md new file mode 100644 index 000000000..fe8899986 --- /dev/null +++ b/.changeset/friendly-lies-camp.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: warn on references to mutated non-state in template diff --git a/.changeset/gentle-sheep-hug.md b/.changeset/gentle-sheep-hug.md new file mode 100644 index 000000000..a2a852449 --- /dev/null +++ b/.changeset/gentle-sheep-hug.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: more validation errors diff --git a/.changeset/great-icons-retire.md b/.changeset/great-icons-retire.md new file mode 100644 index 000000000..53b789db2 --- /dev/null +++ b/.changeset/great-icons-retire.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow `bind:this` with dynamic type on inputs diff --git a/.changeset/green-eggs-approve.md b/.changeset/green-eggs-approve.md new file mode 100644 index 000000000..90c7ec357 --- /dev/null +++ b/.changeset/green-eggs-approve.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: adjust mount and createRoot types diff --git a/.changeset/hungry-tips-unite.md b/.changeset/hungry-tips-unite.md new file mode 100644 index 000000000..b138fd409 --- /dev/null +++ b/.changeset/hungry-tips-unite.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: port over props that were set prior to initialization diff --git a/.changeset/itchy-beans-melt.md b/.changeset/itchy-beans-melt.md new file mode 100644 index 000000000..e6a58d02f --- /dev/null +++ b/.changeset/itchy-beans-melt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: fix compiler errors test suite diff --git a/.changeset/khaki-moose-arrive.md b/.changeset/khaki-moose-arrive.md new file mode 100644 index 000000000..5e7882222 --- /dev/null +++ b/.changeset/khaki-moose-arrive.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow duplicate snippet declaration names diff --git a/.changeset/kind-eagles-join.md b/.changeset/kind-eagles-join.md new file mode 100644 index 000000000..827511c30 --- /dev/null +++ b/.changeset/kind-eagles-join.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: add children to element typings diff --git a/.changeset/large-clouds-carry.md b/.changeset/large-clouds-carry.md new file mode 100644 index 000000000..f9738794f --- /dev/null +++ b/.changeset/large-clouds-carry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: $inspect rune diff --git a/.changeset/lazy-masks-sit.md b/.changeset/lazy-masks-sit.md new file mode 100644 index 000000000..553bbf4a6 --- /dev/null +++ b/.changeset/lazy-masks-sit.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure transitions properly cancel on completion diff --git a/.changeset/lemon-geese-drum.md b/.changeset/lemon-geese-drum.md new file mode 100644 index 000000000..b83013837 --- /dev/null +++ b/.changeset/lemon-geese-drum.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve `$inspect` type definition diff --git a/.changeset/light-pens-watch.md b/.changeset/light-pens-watch.md new file mode 100644 index 000000000..62debd2eb --- /dev/null +++ b/.changeset/light-pens-watch.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: prevent reactive snippet from reinitializing unnecessarily diff --git a/.changeset/lovely-items-turn.md b/.changeset/lovely-items-turn.md new file mode 100644 index 000000000..499b1fadf --- /dev/null +++ b/.changeset/lovely-items-turn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: dont error on stores looking like runes when runes explicitly turned off diff --git a/.changeset/lovely-rules-eat.md b/.changeset/lovely-rules-eat.md new file mode 100644 index 000000000..5ecaa2fc8 --- /dev/null +++ b/.changeset/lovely-rules-eat.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: omit this bind this arg if we know it's not a signal diff --git a/.changeset/moody-frogs-exist.md b/.changeset/moody-frogs-exist.md new file mode 100644 index 000000000..c9eb0c679 --- /dev/null +++ b/.changeset/moody-frogs-exist.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve member expression mutation logic diff --git a/.changeset/new-boats-wait.md b/.changeset/new-boats-wait.md new file mode 100644 index 000000000..b7f09bd0a --- /dev/null +++ b/.changeset/new-boats-wait.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: bump esrap diff --git a/.changeset/ninety-dingos-walk.md b/.changeset/ninety-dingos-walk.md new file mode 100644 index 000000000..9a86f5555 --- /dev/null +++ b/.changeset/ninety-dingos-walk.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: keep fallback value after spread update not setting that prop diff --git a/.changeset/odd-schools-wait.md b/.changeset/odd-schools-wait.md new file mode 100644 index 000000000..11fe1bdba --- /dev/null +++ b/.changeset/odd-schools-wait.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: untrack keyed validation logic diff --git a/.changeset/odd-shoes-cheat.md b/.changeset/odd-shoes-cheat.md new file mode 100644 index 000000000..05f652e2c --- /dev/null +++ b/.changeset/odd-shoes-cheat.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: tweak const tag parsing diff --git a/.changeset/polite-pumpkins-guess.md b/.changeset/polite-pumpkins-guess.md new file mode 100644 index 000000000..e364f9635 --- /dev/null +++ b/.changeset/polite-pumpkins-guess.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: take event attributes into account when checking a11y diff --git a/.changeset/popular-mangos-rest.md b/.changeset/popular-mangos-rest.md new file mode 100644 index 000000000..4293bf3ba --- /dev/null +++ b/.changeset/popular-mangos-rest.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: add $effect.active rune diff --git a/.changeset/pre.json b/.changeset/pre.json index 70c229995..864175e76 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -12,41 +12,93 @@ "afraid-moose-matter", "brave-walls-destroy", "brown-spoons-boil", + "chatty-taxis-juggle", + "chilled-pumas-invite", "chilly-dolphins-lick", "clean-eels-beg", + "cold-birds-own", "cuddly-pianos-drop", "curly-lizards-dream", + "dirty-garlics-design", + "dirty-tips-add", "early-ads-tie", "eight-steaks-shout", "eighty-bikes-camp", + "fair-crabs-check", + "famous-knives-sneeze", + "few-mugs-fail", "fifty-steaks-float", + "flat-melons-protect", + "forty-comics-invent", "fresh-weeks-trade", + "friendly-lies-camp", "funny-wombats-argue", + "gentle-sheep-hug", "good-pianos-jump", + "great-icons-retire", + "green-eggs-approve", + "green-hounds-play", "honest-icons-change", "hungry-dots-fry", + "hungry-tips-unite", "itchy-lions-wash", "khaki-mails-draw", + "khaki-moose-arrive", "kind-deers-lay", + "kind-eagles-join", + "large-clouds-carry", "lazy-spiders-think", + "lemon-geese-drum", + "light-pens-watch", "long-crews-return", + "lovely-items-turn", + "lovely-rules-eat", "lucky-schools-hang", + "moody-frogs-exist", "moody-owls-cry", + "new-boats-wait", + "ninety-dingos-walk", "odd-needles-joke", + "odd-schools-wait", + "odd-shoes-cheat", + "polite-pumpkins-guess", "poor-eggs-enjoy", + "popular-mangos-rest", "quiet-camels-mate", + "rare-pears-whisper", "rich-sheep-burn", "rotten-buckets-develop", + "selfish-tools-hide", + "serious-socks-cover", + "serious-zebras-scream", + "seven-deers-jam", + "seven-ravens-check", "sharp-gorillas-impress", + "shiny-baboons-play", "shiny-shrimps-march", + "slimy-clouds-talk", "small-papayas-laugh", "soft-geese-learn", + "sour-forks-stare", "sour-rules-march", + "stale-comics-look", "strong-lemons-provide", + "swift-ravens-hunt", "tall-shrimps-worry", + "tall-tigers-wait", + "ten-worms-reflect", + "thin-foxes-lick", "thirty-flowers-sit", "thirty-ghosts-fix", + "thirty-impalas-repair", + "thirty-wombats-relax", "tiny-kings-whisper", - "wicked-doors-train" + "twelve-onions-juggle", + "two-dragons-yell", + "two-falcons-buy", + "wet-games-fly", + "wicked-clouds-exercise", + "wicked-doors-train", + "witty-camels-warn" ] } diff --git a/.changeset/rare-pears-whisper.md b/.changeset/rare-pears-whisper.md new file mode 100644 index 000000000..05dc333b3 --- /dev/null +++ b/.changeset/rare-pears-whisper.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: add $effect.root rune diff --git a/.changeset/selfish-tools-hide.md b/.changeset/selfish-tools-hide.md new file mode 100644 index 000000000..194a2439d --- /dev/null +++ b/.changeset/selfish-tools-hide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure computed props are cached with derived diff --git a/.changeset/serious-socks-cover.md b/.changeset/serious-socks-cover.md new file mode 100644 index 000000000..709bb95be --- /dev/null +++ b/.changeset/serious-socks-cover.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure event handlers containing arguments are not hoisted diff --git a/.changeset/serious-zebras-scream.md b/.changeset/serious-zebras-scream.md new file mode 100644 index 000000000..9efd62e5b --- /dev/null +++ b/.changeset/serious-zebras-scream.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure snippets have correct scope diff --git a/.changeset/seven-deers-jam.md b/.changeset/seven-deers-jam.md new file mode 100644 index 000000000..cdb0c8d16 --- /dev/null +++ b/.changeset/seven-deers-jam.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: better attribute casing logic diff --git a/.changeset/seven-ravens-check.md b/.changeset/seven-ravens-check.md new file mode 100644 index 000000000..0bbfad1b2 --- /dev/null +++ b/.changeset/seven-ravens-check.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: support type definition in {@const} diff --git a/.changeset/slimy-clouds-talk.md b/.changeset/slimy-clouds-talk.md new file mode 100644 index 000000000..6ff121904 --- /dev/null +++ b/.changeset/slimy-clouds-talk.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: ignore `src`, `srcset`, and `href` attributes when hydrating diff --git a/.changeset/sour-forks-stare.md b/.changeset/sour-forks-stare.md new file mode 100644 index 000000000..7bdd4433c --- /dev/null +++ b/.changeset/sour-forks-stare.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: remove redundant hydration code diff --git a/.changeset/spicy-plums-admire.md b/.changeset/spicy-plums-admire.md new file mode 100644 index 000000000..9ba9e5313 --- /dev/null +++ b/.changeset/spicy-plums-admire.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: apply event attribute validation to elements only diff --git a/.changeset/stale-comics-look.md b/.changeset/stale-comics-look.md new file mode 100644 index 000000000..b7f09bd0a --- /dev/null +++ b/.changeset/stale-comics-look.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: bump esrap diff --git a/.changeset/sweet-mangos-beg.md b/.changeset/sweet-mangos-beg.md new file mode 100644 index 000000000..3a0adfc03 --- /dev/null +++ b/.changeset/sweet-mangos-beg.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: handle css nth-selector syntax diff --git a/.changeset/swift-ravens-hunt.md b/.changeset/swift-ravens-hunt.md new file mode 100644 index 000000000..d62ff1ca5 --- /dev/null +++ b/.changeset/swift-ravens-hunt.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve template text node serialization diff --git a/.changeset/tall-tigers-wait.md b/.changeset/tall-tigers-wait.md new file mode 100644 index 000000000..a71fdef9a --- /dev/null +++ b/.changeset/tall-tigers-wait.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve infinite loop capturing diff --git a/.changeset/ten-worms-reflect.md b/.changeset/ten-worms-reflect.md new file mode 100644 index 000000000..a78d6b052 --- /dev/null +++ b/.changeset/ten-worms-reflect.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: remove constructor overload diff --git a/.changeset/thin-foxes-lick.md b/.changeset/thin-foxes-lick.md new file mode 100644 index 000000000..904c84cc5 --- /dev/null +++ b/.changeset/thin-foxes-lick.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: improve `` generated code diff --git a/.changeset/thirty-wombats-relax.md b/.changeset/thirty-wombats-relax.md new file mode 100644 index 000000000..0ed0f40a7 --- /dev/null +++ b/.changeset/thirty-wombats-relax.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve each block index handling diff --git a/.changeset/twelve-onions-juggle.md b/.changeset/twelve-onions-juggle.md new file mode 100644 index 000000000..00018fce3 --- /dev/null +++ b/.changeset/twelve-onions-juggle.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: handle ts expressions when dealing with runes diff --git a/.changeset/two-dragons-yell.md b/.changeset/two-dragons-yell.md new file mode 100644 index 000000000..f6f7e1753 --- /dev/null +++ b/.changeset/two-dragons-yell.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly inspect derived values diff --git a/.changeset/two-falcons-buy.md b/.changeset/two-falcons-buy.md new file mode 100644 index 000000000..c1a8e069f --- /dev/null +++ b/.changeset/two-falcons-buy.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: remove unused code diff --git a/.changeset/wet-games-fly.md b/.changeset/wet-games-fly.md new file mode 100644 index 000000000..9f03e2688 --- /dev/null +++ b/.changeset/wet-games-fly.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: type-level back-compat for default slot and children prop diff --git a/.changeset/witty-camels-warn.md b/.changeset/witty-camels-warn.md new file mode 100644 index 000000000..cdcccd6ac --- /dev/null +++ b/.changeset/witty-camels-warn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: prevent some unused variable creation diff --git a/.prettierignore b/.prettierignore index 36dcaeb08..45e12c6e3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -37,3 +37,7 @@ sites/svelte.dev/src/lib/generated .changeset pnpm-lock.yaml pnpm-workspace.yaml + +# Temporarily ignore this file to avoid merge conflicts. +# see: https://github.com/sveltejs/svelte/pull/9609 +documentation/docs/05-misc/03-typescript.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 924a0a752..167a62646 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ The maintainers meet on the final Saturday of each month. While these meetings a ### Prioritization -We do our best to review PRs and RFCs as they are sent, but it is difficult to keep up. We welcome help in reviewing PRs, RFC, and issues. If an item aligns with the current priority on our [roadmap](https://svelte.dev/roadmap), it is more likely to be reviewed quickly. PRs to the most important and active ones repositories get reviewed more quickly while PRs to smaller inactive repos may sit for a bit before we periodically come by and review the pending PRs in a batch. +We do our best to review PRs and RFCs as they are sent, but it is difficult to keep up. We welcome help in reviewing PRs, RFCs, and issues. If an item aligns with the current priority on our [roadmap](https://svelte.dev/roadmap), it is more likely to be reviewed quickly. PRs to the most important and active ones repositories get reviewed more quickly while PRs to smaller inactive repos may sit for a bit before we periodically come by and review the pending PRs in a batch. ## Bugs @@ -74,10 +74,11 @@ Small pull requests are much easier to review and more likely to get merged. ### Installation -1. Ensure you have [pnpm](https://pnpm.io/installation) installed -1. After cloning the repository, run `pnpm install`. You can do this in the root directory or in the `svelte` project -1. Move into the `svelte` directory with `cd packages/svelte` -1. To compile in watch mode, run `pnpm dev` +Ensure you have [pnpm](https://pnpm.io/installation) installed. After cloning the repository, run `pnpm install`. + +### Developing + +To build the UMD version of `svelte/compiler` (this is only necessary for CommonJS consumers, or in-browser use), run `pnpm build` inside `packages/svelte`. To rebuild whenever source files change, run `pnpm dev`. ### Creating a branch @@ -100,18 +101,28 @@ Test samples are kept in `/test/xxx/samples` folder. > PREREQUISITE: Install chromium via playwright by running `pnpm playwright install chromium` 1. To run test, run `pnpm test`. -1. To run test for a specific feature, you can use the `-g` (aka `--grep`) option. For example, to only run test involving transitions, run `pnpm test -- -g transition`. +1. To run a particular test suite, use `pnpm test `, for example: + + ```bash + pnpm test validator + ``` -##### Running solo test +1. To filter tests _within_ a test suite, use `pnpm test -- -t `, for example: -1. To run only one test, rename the test sample folder to end with `.solo`. For example, to run the `test/js/samples/action` only, rename it to `test/js/samples/action.solo`. -1. To run only one test suite, rename the test suite folder to end with `.solo`. For example, to run the `test/js` test suite only, rename it to `test/js.solo`. -1. Remember to rename the test folder back. The CI will fail if there's a solo test. + ```bash + pnpm test validator -- -t a11y-alt-text + ``` + + (You can also do `FILTER= pnpm test ` which removes other tests rather than simply skipping them — this will result in faster and more compact test results, but it's non-idiomatic. Choose your fighter.) ##### Updating `.expected` files -1. Tests suites like `css`, `js`, `server-side-rendering` asserts that the generated output has to match the content in the `.expected` file. For example, in the `js` test suites, the generated js code is compared against the content in `expected.js`. -1. To update the content of the `.expected` file, run the test with `--update` flag. (`pnpm test --update`) +1. Tests suites like `snapshot` and `parser` assert that the generated output matches the existing snapshot. +1. To update these snapshots, run `UPDATE_SNAPSHOTS=true pnpm test`. + +### Typechecking + +To typecheck the codebase, run `pnpm check` inside `packages/svelte`. To typecheck in watch mode, run `pnpm check:watch`. ### Style guide diff --git a/README.md b/README.md index 89e2b65c6..41932870f 100644 --- a/README.md +++ b/README.md @@ -102,47 +102,7 @@ You may view [our roadmap](https://svelte.dev/roadmap) if you'd like to see what ## Contributing -Please see the [Contributing Guide](CONTRIBUTING.md) and [svelte package](packages/svelte) for contributing to Svelte. - -### Development - -Pull requests are encouraged and always welcome. [Pick an issue](https://github.com/sveltejs/svelte/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) and help us out! - -To install and work on Svelte locally: - -```bash -git clone https://github.com/sveltejs/svelte.git -cd svelte -pnpm install -``` - -> Do not use Yarn to install the dependencies, as the specific package versions in `pnpm-lock.json` are used to build and test Svelte. - -To build the compiler and all the other modules included in the package: - -```bash -pnpm build -``` - -To watch for changes and continually rebuild the package (this is useful if you're using [`pnpm link`](https://pnpm.io/cli/link) to test out changes in a project locally): - -```bash -pnpm dev -``` - -The compiler is written in JavaScript and uses [JSDoc](https://jsdoc.app/index.html) comments for type-checking. - -### Running Tests - -```bash -pnpm test -``` - -To filter tests, use `-g` (aka `--grep`). For example, to only run tests involving transitions: - -```bash -pnpm test -- -g transition -``` +Please see the [Contributing Guide](CONTRIBUTING.md) and the [`svelte`](packages/svelte) package for information on contributing to Svelte. ### svelte.dev diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index d883faef6..e94b5db20 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,147 @@ # svelte +## 5.0.0-next.18 + +### Patch Changes + +- feat: proxied staet ([#9739](https://github.com/sveltejs/svelte/pull/9739)) + +- chore: more validation errors ([#9723](https://github.com/sveltejs/svelte/pull/9723)) + +- fix: allow duplicate snippet declaration names ([#9759](https://github.com/sveltejs/svelte/pull/9759)) + +- fix: ensure computed props are cached with derived ([#9757](https://github.com/sveltejs/svelte/pull/9757)) + +- fix: ensure event handlers containing arguments are not hoisted ([#9758](https://github.com/sveltejs/svelte/pull/9758)) + +## 5.0.0-next.17 + +### Patch Changes + +- fix: improve `$inspect` type definition ([#9731](https://github.com/sveltejs/svelte/pull/9731)) + +- fix: correctly inspect derived values ([#9731](https://github.com/sveltejs/svelte/pull/9731)) + +## 5.0.0-next.16 + +### Patch Changes + +- fix: delegate events on elements with bind-this ([#9696](https://github.com/sveltejs/svelte/pull/9696)) + +- fix: ensure implicit children snippet renders correctly ([#9706](https://github.com/sveltejs/svelte/pull/9706)) + +- fix: ensure `$slots` exists in runes mode ([#9718](https://github.com/sveltejs/svelte/pull/9718)) + +- fix: allow `bind:this` with dynamic type on inputs ([#9713](https://github.com/sveltejs/svelte/pull/9713)) + +- fix: port over props that were set prior to initialization ([#9704](https://github.com/sveltejs/svelte/pull/9704)) + +- feat: $inspect rune ([#9705](https://github.com/sveltejs/svelte/pull/9705)) + +- fix: keep fallback value after spread update not setting that prop ([#9717](https://github.com/sveltejs/svelte/pull/9717)) + +- fix: tweak const tag parsing ([#9715](https://github.com/sveltejs/svelte/pull/9715)) + +- chore: remove redundant hydration code ([#9698](https://github.com/sveltejs/svelte/pull/9698)) + +- fix: improve template text node serialization ([#9722](https://github.com/sveltejs/svelte/pull/9722)) + +- fix: improve infinite loop capturing ([#9721](https://github.com/sveltejs/svelte/pull/9721)) + +## 5.0.0-next.15 + +### Patch Changes + +- fix: add children to element typings ([#9679](https://github.com/sveltejs/svelte/pull/9679)) + +- fix: handle ts expressions when dealing with runes ([#9681](https://github.com/sveltejs/svelte/pull/9681)) + +## 5.0.0-next.14 + +### Patch Changes + +- feat: warn on references to mutated non-state in template ([#9669](https://github.com/sveltejs/svelte/pull/9669)) + +- fix: prevent reactive snippet from reinitializing unnecessarily ([#9665](https://github.com/sveltejs/svelte/pull/9665)) + +- fix: take event attributes into account when checking a11y ([#9664](https://github.com/sveltejs/svelte/pull/9664)) + +- feat: add $effect.root rune ([#9638](https://github.com/sveltejs/svelte/pull/9638)) + +- feat: support type definition in {@const} ([#9609](https://github.com/sveltejs/svelte/pull/9609)) + +- feat: ignore `src`, `srcset`, and `href` attributes when hydrating ([#9662](https://github.com/sveltejs/svelte/pull/9662)) + +- chore: bump esrap ([#9649](https://github.com/sveltejs/svelte/pull/9649)) + +- chore: improve `` generated code ([#9648](https://github.com/sveltejs/svelte/pull/9648)) + +- chore: prevent some unused variable creation ([#9571](https://github.com/sveltejs/svelte/pull/9571)) + +## 5.0.0-next.13 + +### Patch Changes + +- fix: apply keyed validation only for keyed each ([#9641](https://github.com/sveltejs/svelte/pull/9641)) + +- fix: omit this bind this arg if we know it's not a signal ([#9635](https://github.com/sveltejs/svelte/pull/9635)) + +- fix: improve each block index handling ([#9644](https://github.com/sveltejs/svelte/pull/9644)) + +## 5.0.0-next.12 + +### Patch Changes + +- fix: adjust mount and createRoot types ([`63e583184`](https://github.com/sveltejs/svelte/commit/63e58318460dbb3485df93d15beb2779a86d2c9a)) + +- fix: remove constructor overload ([`cb4b1f0a1`](https://github.com/sveltejs/svelte/commit/cb4b1f0a189803bed04adcb90fbd4334782e8469)) + +- fix: type-level back-compat for default slot and children prop ([`a3bc7d569`](https://github.com/sveltejs/svelte/commit/a3bc7d5698425ec9dde86eb302f2fd56d9da8f96)) + +## 5.0.0-next.11 + +### Patch Changes + +- feat: add type of `$effect.active` ([#9624](https://github.com/sveltejs/svelte/pull/9624)) + +- fix: correct bind this multiple bindings ([#9617](https://github.com/sveltejs/svelte/pull/9617)) + +- chore: reuse common templates ([#9601](https://github.com/sveltejs/svelte/pull/9601)) + +- fix: handle undefined bubble events ([#9614](https://github.com/sveltejs/svelte/pull/9614)) + +- fix: dont error on stores looking like runes when runes explicitly turned off ([#9615](https://github.com/sveltejs/svelte/pull/9615)) + +- fix: improve member expression mutation logic ([#9625](https://github.com/sveltejs/svelte/pull/9625)) + +- chore: untrack keyed validation logic ([#9618](https://github.com/sveltejs/svelte/pull/9618)) + +- fix: ensure snippets have correct scope ([#9623](https://github.com/sveltejs/svelte/pull/9623)) + +- fix: better attribute casing logic ([#9626](https://github.com/sveltejs/svelte/pull/9626)) + +## 5.0.0-next.10 + +### Patch Changes + +- chore: add inline new class warning ([#9583](https://github.com/sveltejs/svelte/pull/9583)) + +- fix: prevent false positives when detecting runes mode ([#9599](https://github.com/sveltejs/svelte/pull/9599)) + +- fix: deconflict generated names against globals ([#9570](https://github.com/sveltejs/svelte/pull/9570)) + +- chore: bump esrap ([#9590](https://github.com/sveltejs/svelte/pull/9590)) + +- feat: add $effect.active rune ([#9591](https://github.com/sveltejs/svelte/pull/9591)) + +- feat: add Snippet type ([#9584](https://github.com/sveltejs/svelte/pull/9584)) + +- fix: adjust event delegation heuristics ([#9581](https://github.com/sveltejs/svelte/pull/9581)) + +- chore: remove unused code ([#9593](https://github.com/sveltejs/svelte/pull/9593)) + +- fix: adjust regex ([#9572](https://github.com/sveltejs/svelte/pull/9572)) + ## 5.0.0-next.9 ### Patch Changes diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index db32222c6..4c3bef74f 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -64,6 +64,10 @@ export type MessageEventHandler = EventHandler { + // Implicit children prop every element has + // Add this here so that libraries doing `$props()` don't need a separate interface + children?: import('svelte').Snippet; + // Clipboard Events 'on:copy'?: ClipboardEventHandler | undefined | null; oncopy?: ClipboardEventHandler | undefined | null; diff --git a/packages/svelte/package.json b/packages/svelte/package.json index ab1584070..d34a677f7 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.0.0-next.9", + "version": "5.0.0-next.18", "type": "module", "types": "./types/index.d.ts", "engines": { @@ -91,7 +91,7 @@ ], "scripts": { "build": "rollup -c && node scripts/build.js && node scripts/check-treeshakeability.js", - "watch": "rollup -cw", + "dev": "rollup -cw", "check": "tsc && cd ./tests/types && tsc", "check:watch": "tsc --watch", "generate:version": "node ./scripts/generate-version.js", @@ -120,7 +120,7 @@ "aria-query": "^5.3.0", "axobject-query": "^4.0.0", "esm-env": "^1.0.0", - "esrap": "^1.1.1", + "esrap": "^1.2.1", "is-reference": "^3.0.1", "locate-character": "^3.0.0", "magic-string": "^0.30.4", diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 700ec21e1..8d01f4ac9 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -24,9 +24,11 @@ const internal = { const parse = { /** @param {string} name */ 'unclosed-element': (name) => `<${name}> was left open`, - 'unclosed-block': () => `block was left open`, + 'unclosed-block': () => `Block was left open`, 'unexpected-block-close': () => `Unexpected block closing tag`, - 'unexpected-eof': () => `Unexpected end of input`, + /** @param {string} [expected] */ + 'unexpected-eof': (expected) => + `Unexpected end of input` + (expected ? ` (expected ${expected})` : ''), /** @param {string} message */ 'js-parse-error': (message) => message, /** @param {string} token */ @@ -39,17 +41,15 @@ const parse = { 'invalid-script-context': () => `If the context attribute is supplied, its value must be "module"`, 'invalid-elseif': () => `'elseif' should be 'else if'`, - /** - * @param {string} child - * @param {string} parent - */ - 'invalid-block-parent': (child, parent) => - `Expected to close ${parent} before seeing ${child} block`, + 'invalid-continuing-block-placement': () => + `{:...} block is invalid at this position (did you forget to close the preceeding element or block?)`, /** * @param {string} child * @param {string} parent */ 'invalid-block-missing-parent': (child, parent) => `${child} block must be a child of ${parent}`, + /** @param {string} name */ + 'duplicate-block-part': (name) => `${name} cannot appear more than once within a block`, 'expected-block-type': () => `Expected 'if', 'each', 'await', 'key' or 'snippet'`, 'expected-identifier': () => `Expected an identifier`, 'invalid-debug': () => `{@debug ...} arguments must be identifiers, not arbitrary expressions`, @@ -98,12 +98,9 @@ const css = { 'invalid-css-empty-declaration': () => `Declaration cannot be empty`, 'invalid-css-global-placement': () => `:global(...) can be at the start or end of a selector sequence, but not in the middle`, - 'invalid-css-global-selector': () => `:global(...) must contain exactly one selector`, - 'invalid-css-global-selector-list': () => `:global(...) cannot be used to modify a selector, or be modified by another selector`, - 'invalid-css-selector': () => `Invalid selector`, 'invalid-css-identifier': () => 'Expected a valid CSS identifier' }; @@ -132,9 +129,12 @@ const special_elements = { 'invalid-customElement-shadow-attribute': () => '"shadow" must be either "open" or "none"', 'unknown-svelte-option-attribute': /** @param {string} name */ (name) => ` unknown attribute '${name}'`, + 'illegal-svelte-head-attribute': () => ' cannot have attributes nor directives', 'invalid-svelte-fragment-attribute': () => ` can only have a slot attribute and (optionally) a let: directive`, 'invalid-svelte-fragment-slot': () => ` slot attribute must have a static value`, + 'invalid-svelte-fragment-placement': () => + ` must be the direct child of a component`, /** @param {string} name */ 'invalid-svelte-element-placement': (name) => `<${name}> tags cannot be inside elements or blocks`, @@ -164,8 +164,8 @@ const runes = { 'invalid-legacy-export': () => `Cannot use \`export let\` in runes mode — use $props instead`, /** @param {string} rune */ 'invalid-rune-usage': (rune) => `Cannot use ${rune} rune in non-runes mode`, - /** @param {string} rune */ - 'invalid-rune-export': (rune) => `Cannot export value created with ${rune}`, + 'invalid-state-export': () => `Cannot export state if it is reassigned`, + 'invalid-derived-export': () => `Cannot export derived state`, 'invalid-props-id': () => `$props() can only be used with an object destructuring pattern`, 'invalid-props-pattern': () => `$props() assignment must not contain nested properties or computed keys`, @@ -211,12 +211,14 @@ const elements = { * @param {string} node * @param {string} parent */ - 'invalid-node-placement': (node, parent) => `${node} is invalid inside <${parent}>` + 'invalid-node-placement': (node, parent) => `${node} is invalid inside <${parent}>`, + 'illegal-title-attribute': () => ' cannot have attributes nor directives', + 'invalid-title-content': () => '<title> can only contain text and {tags}' }; /** @satisfies {Errors} */ const components = { - 'invalid-component-directive': () => `Directive is not valid on components` + 'invalid-component-directive': () => `This type of directive is not valid on components` }; /** @satisfies {Errors} */ @@ -224,18 +226,60 @@ const attributes = { 'empty-attribute-shorthand': () => `Attribute shorthand cannot be empty`, 'duplicate-attribute': () => `Attributes need to be unique`, 'invalid-event-attribute-value': () => - `Event attribute must be a JavaScript expression, not a string` + `Event attribute must be a JavaScript expression, not a string`, + /** @param {string} name */ + 'invalid-attribute-name': (name) => `'${name}' is not a valid attribute name`, + /** @param {'no-each' | 'each-key' | 'child'} type */ + 'invalid-animation': (type) => + type === 'no-each' + ? `An element that uses the animate directive must be the immediate child of a keyed each block` + : type === 'each-key' + ? `An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?` + : `An element that uses the animate directive must be the sole child of a keyed each block`, + 'duplicate-animation': () => `An element can only have one 'animate' directive`, + /** @param {string[] | undefined} [modifiers] */ + 'invalid-event-modifier': (modifiers) => + modifiers + ? `Valid event modifiers are ${modifiers.slice(0, -1).join(', ')} or ${modifiers.slice(-1)}` + : `Event modifiers other than 'once' can only be used on DOM elements`, + /** + * @param {string} modifier1 + * @param {string} modifier2 + */ + 'invalid-event-modifier-combination': (modifier1, modifier2) => + `The '${modifier1}' and '${modifier2}' modifiers cannot be used together`, + /** + * @param {string} directive1 + * @param {string} directive2 + */ + 'duplicate-transition': (directive1, directive2) => { + /** @param {string} _directive */ + function describe(_directive) { + return _directive === 'transition' ? "a 'transition'" : `an '${_directive}'`; + } + + return directive1 === directive2 + ? `An element can only have one '${directive1}' directive` + : `An element cannot have both ${describe(directive1)} directive and ${describe( + directive2 + )} directive`; + }, + 'invalid-let-directive-placement': () => 'let directive at invalid position' }; /** @satisfies {Errors} */ const slots = { 'invalid-slot-element-attribute': () => `<slot> can only receive attributes, not directives`, 'invalid-slot-attribute': () => `slot attribute must be a static value`, - 'invalid-slot-name': () => `slot attribute must be a static value`, + /** @param {boolean} is_default */ + 'invalid-slot-name': (is_default) => + is_default + ? `default is a reserved word — it cannot be used as a slot name` + : `slot attribute must be a static value`, 'invalid-slot-placement': () => `Element with a slot='...' attribute must be a child of a component or a descendant of a custom element`, - 'duplicate-slot-name': /** @param {string} name @param {string} component */ (name, component) => - `Duplicate slot name '${name}' in <${component}>`, + /** @param {string} name @param {string} component */ + 'duplicate-slot-name': (name, component) => `Duplicate slot name '${name}' in <${component}>`, 'invalid-default-slot-content': () => `Found default slot content alongside an explicit slot="default"` }; @@ -256,13 +300,20 @@ const bindings = { 'invalid-type-attribute': () => `'type' attribute must be a static text value if input uses two-way binding`, 'invalid-multiple-attribute': () => - `'multiple' attribute must be static if select uses two-way binding` + `'multiple' attribute must be static if select uses two-way binding`, + 'missing-contenteditable-attribute': () => + `'contenteditable' attribute is required for textContent, innerHTML and innerText two-way bindings`, + 'dynamic-contenteditable-attribute': () => + `'contenteditable' attribute cannot be dynamic if element uses two-way binding` }; /** @satisfies {Errors} */ const variables = { 'illegal-global': /** @param {string} name */ (name) => - `${name} is an illegal variable name. To reference a global variable called ${name}, use globalThis.${name}` + `${name} is an illegal variable name. To reference a global variable called ${name}, use globalThis.${name}`, + /** @param {string} name */ + 'duplicate-declaration': (name) => `'${name}' has already been declared`, + 'default-export': () => `A component cannot have a default export` }; /** @satisfies {Errors} */ @@ -279,6 +330,12 @@ const compiler_options = { 'removed-compiler-option': (msg) => `Invalid compiler option: ${msg}` }; +/** @satisfies {Errors} */ +const const_tag = { + 'invalid-const-placement': () => + `{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>` +}; + /** @satisfies {Errors} */ const errors = { ...internal, @@ -293,7 +350,8 @@ const errors = { ...bindings, ...variables, ...compiler_options, - ...legacy_reactivity + ...legacy_reactivity, + ...const_tag // missing_contenteditable_attribute: { // code: 'missing-contenteditable-attribute', @@ -304,34 +362,11 @@ const errors = { // code: 'dynamic-contenteditable-attribute', // message: "'contenteditable' attribute cannot be dynamic if element uses two-way binding" // }, - // invalid_event_modifier_combination: /** - // * @param {string} modifier1 - // * @param {string} modifier2 - // */ (modifier1, modifier2) => ({ - // code: 'invalid-event-modifier', - // message: `The '${modifier1}' and '${modifier2}' modifiers cannot be used together` - // }), - // invalid_event_modifier_legacy: /** @param {string} modifier */ (modifier) => ({ - // code: 'invalid-event-modifier', - // message: `The '${modifier}' modifier cannot be used in legacy mode` - // }), - // invalid_event_modifier: /** @param {string} valid */ (valid) => ({ - // code: 'invalid-event-modifier', - // message: `Valid event modifiers are ${valid}` - // }), - // invalid_event_modifier_component: { - // code: 'invalid-event-modifier', - // message: "Event modifiers other than 'once' can only be used on DOM elements" - // }, // textarea_duplicate_value: { // code: 'textarea-duplicate-value', // message: // 'A <textarea> can have either a value attribute or (equivalently) child content, but not both' // }, - // illegal_attribute: /** @param {string} name */ (name) => ({ - // code: 'illegal-attribute', - // message: `'${name}' is not a valid attribute name` - // }), // invalid_attribute_head: { // code: 'invalid-attribute', // message: '<svelte:head> should not have any attributes or directives' @@ -340,10 +375,6 @@ const errors = { // code: 'invalid-action', // message: 'Actions can only be applied to DOM elements, not components' // }, - // invalid_animation: { - // code: 'invalid-animation', - // message: 'Animations can only be applied to DOM elements, not components' - // }, // invalid_class: { // code: 'invalid-class', // message: 'Classes can only be applied to DOM elements, not components' @@ -364,22 +395,10 @@ const errors = { // code: 'dynamic-slot-name', // message: '<slot> name cannot be dynamic' // }, - // invalid_slot_name: { - // code: 'invalid-slot-name', - // message: 'default is a reserved word — it cannot be used as a slot name' - // }, // invalid_slot_attribute_value_missing: { // code: 'invalid-slot-attribute', // message: 'slot attribute value is missing' // }, - // invalid_slotted_content_fragment: { - // code: 'invalid-slotted-content', - // message: '<svelte:fragment> must be a child of a component' - // }, - // illegal_attribute_title: { - // code: 'illegal-attribute', - // message: '<title> cannot have attributes' - // }, // illegal_structure_title: { // code: 'illegal-structure', // message: '<title> can only contain text and {tags}' @@ -428,10 +447,6 @@ const errors = { // code: 'illegal-variable-declaration', // message: 'Cannot declare same variable name which is imported inside <script context="module">' // }, - // css_invalid_global: { - // code: 'css-invalid-global', - // message: ':global(...) can be at the start or end of a selector sequence, but not in the middle' - // }, // css_invalid_global_selector: { // code: 'css-invalid-global-selector', // message: ':global(...) must contain a single selector' @@ -445,55 +460,15 @@ const errors = { // code: 'css-invalid-selector', // message: `Invalid selector "${selector}"` // }), - // duplicate_animation: { - // code: 'duplicate-animation', - // message: "An element can only have one 'animate' directive" - // }, - // invalid_animation_immediate: { - // code: 'invalid-animation', - // message: - // 'An element that uses the animate directive must be the immediate child of a keyed each block' - // }, - // invalid_animation_key: { - // code: 'invalid-animation', - // message: - // 'An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?' - // }, - // invalid_animation_sole: { - // code: 'invalid-animation', - // message: - // 'An element that uses the animate directive must be the sole child of a keyed each block' - // }, - // invalid_animation_dynamic_element: { - // code: 'invalid-animation', - // message: '<svelte:element> cannot have a animate directive' - // }, // invalid_directive_value: { // code: 'invalid-directive-value', // message: // 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)' // }, - // invalid_const_placement: { - // code: 'invalid-const-placement', - // message: - // '{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>' - // }, - // invalid_const_declaration: /** @param {string} name */ (name) => ({ - // code: 'invalid-const-declaration', - // message: `'${name}' has already been declared` - // }), - // invalid_const_update: /** @param {string} name */ (name) => ({ - // code: 'invalid-const-update', - // message: `'${name}' is declared using {@const ...} and is read-only` - // }), // cyclical_const_tags: /** @param {string[]} cycle */ (cycle) => ({ // code: 'cyclical-const-tags', // message: `Cyclical dependency detected: ${cycle.join(' → ')}` // }), - // invalid_component_style_directive: { - // code: 'invalid-component-style-directive', - // message: 'Style directives cannot be used on components' - // }, // invalid_var_declaration: { // code: 'invalid_var_declaration', // message: '"var" scope should not extend outside the reactive block' diff --git a/packages/svelte/src/compiler/legacy.js b/packages/svelte/src/compiler/legacy.js index 6a0c7f108..da20b9069 100644 --- a/packages/svelte/src/compiler/legacy.js +++ b/packages/svelte/src/compiler/legacy.js @@ -209,6 +209,33 @@ export function convert(source, ast) { }; }, // @ts-ignore + ConstTag(node) { + if ( + /** @type {import('./types/legacy-nodes.js').LegacyConstTag} */ (node).expression !== + undefined + ) { + return node; + } + + const modern_node = /** @type {import('#compiler').ConstTag} */ (node); + const { id: left } = { ...modern_node.declaration.declarations[0] }; + // @ts-ignore + delete left.typeAnnotation; + return { + type: 'ConstTag', + start: modern_node.start, + end: node.end, + expression: { + type: 'AssignmentExpression', + start: (modern_node.declaration.start ?? 0) + 'const '.length, + end: modern_node.declaration.end ?? 0, + operator: '=', + left, + right: modern_node.declaration.declarations[0].init + } + }; + }, + // @ts-ignore KeyBlock(node, { visit }) { remove_surrounding_whitespace_nodes(node.fragment.nodes); return { diff --git a/packages/svelte/src/compiler/phases/1-parse/acorn.js b/packages/svelte/src/compiler/phases/1-parse/acorn.js index dfc87d90a..ad23d47d6 100644 --- a/packages/svelte/src/compiler/phases/1-parse/acorn.js +++ b/packages/svelte/src/compiler/phases/1-parse/acorn.js @@ -58,7 +58,6 @@ export function get_comment_handlers(source) { * @typedef {import('estree').Comment & { * start: number; * end: number; - * has_trailing_newline?: boolean * }} CommentWithLocation */ @@ -91,35 +90,26 @@ export function get_comment_handlers(source) { add_comments(ast) { if (comments.length === 0) return; - walk( - ast, - {}, - { - _(node, { next }) { - let comment; + walk(ast, null, { + _(node, { next }) { + let comment; - while (comments[0] && comments[0].start < node.start) { - comment = /** @type {CommentWithLocation} */ (comments.shift()); - - const next = comments[0] || node; - comment.has_trailing_newline = - comment.type === 'Line' || /\n/.test(source.slice(comment.end, next.start)); - - (node.leadingComments ||= []).push(comment); - } + while (comments[0] && comments[0].start < node.start) { + comment = /** @type {CommentWithLocation} */ (comments.shift()); + (node.leadingComments ||= []).push(comment); + } - next(); + next(); - if (comments[0]) { - const slice = source.slice(node.end, comments[0].start); + if (comments[0]) { + const slice = source.slice(node.end, comments[0].start); - if (/^[,) \t]*$/.test(slice)) { - node.trailingComments = [/** @type {CommentWithLocation} */ (comments.shift())]; - } + if (/^[,) \t]*$/.test(slice)) { + node.trailingComments = [/** @type {CommentWithLocation} */ (comments.shift())]; } } } - ); + }); } }; } diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 8c1bb174f..6b7642c25 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -77,8 +77,10 @@ export class Parser { const current = this.current(); if (current.type === 'RegularElement') { + current.end = current.start + 1; error(current, 'unclosed-element', current.name); } else { + current.end = current.start + 1; error(current, 'unclosed-block'); } } @@ -145,7 +147,7 @@ export class Parser { if (required) { if (this.index === this.template.length) { - error(this.index, 'unexpected-eof'); + error(this.index, 'unexpected-eof', str); } else { error(this.index, 'expected-token', str); } @@ -156,7 +158,13 @@ export class Parser { /** @param {string} str */ match(str) { - return this.template.slice(this.index, this.index + str.length) === str; + const length = str.length; + if (length === 1) { + // more performant than slicing + return this.template[this.index] === str; + } + + return this.template.slice(this.index, this.index + length) === str; } /** diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index 34d22bc7c..9f59e8205 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -1,12 +1,12 @@ import { error } from '../../../errors.js'; const REGEX_MATCHER = /^[~^$*|]?=/; -const REGEX_CLOSING_PAREN = /\)/; const REGEX_CLOSING_BRACKET = /[\s\]]/; const REGEX_ATTRIBUTE_FLAGS = /^[a-zA-Z]+/; // only `i` and `s` are valid today, but make it future-proof const REGEX_COMBINATOR_WHITESPACE = /^\s*(\+|~|>|\|\|)\s*/; const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/; const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/; +const REGEX_NTH_OF = /^(even|odd|(-?[0-9]?n?(\s*\+\s*[0-9]+)?))(\s+of\s+)?/; const REGEX_WHITESPACE_OR_COLON = /[\s:]/; const REGEX_BRACE_OR_SEMICOLON = /[{;]/; const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/; @@ -145,22 +145,23 @@ function read_rule(parser) { /** * @param {import('../index.js').Parser} parser + * @param {boolean} [inside_pseudo_class] * @returns {import('#compiler').Css.SelectorList} */ -function read_selector_list(parser) { +function read_selector_list(parser, inside_pseudo_class = false) { /** @type {import('#compiler').Css.Selector[]} */ const children = []; const start = parser.index; while (parser.index < parser.template.length) { - children.push(read_selector(parser)); + children.push(read_selector(parser, inside_pseudo_class)); const end = parser.index; parser.allow_whitespace(); - if (parser.match('{')) { + if (inside_pseudo_class ? parser.match(')') : parser.match('{')) { return { type: 'SelectorList', start, @@ -178,9 +179,10 @@ function read_selector_list(parser) { /** * @param {import('../index.js').Parser} parser + * @param {boolean} [inside_pseudo_class] * @returns {import('#compiler').Css.Selector} */ -function read_selector(parser) { +function read_selector(parser, inside_pseudo_class = false) { const list_start = parser.index; /** @type {Array<import('#compiler').Css.SimpleSelector | import('#compiler').Css.Combinator>} */ @@ -190,9 +192,16 @@ function read_selector(parser) { const start = parser.index; if (parser.eat('*')) { + let name = '*'; + if (parser.match('|')) { + // * is the namespace (which we ignore) + parser.index++; + name = read_identifier(parser); + } + children.push({ type: 'TypeSelector', - name: '*', + name, start, end: parser.index }); @@ -220,12 +229,14 @@ function read_selector(parser) { } else if (parser.eat(':')) { const name = read_identifier(parser); - /** @type {string | null} */ + /** @type {null | import('#compiler').Css.SelectorList} */ let args = null; if (parser.eat('(')) { - args = parser.read_until(REGEX_CLOSING_PAREN); + args = read_selector_list(parser, true); parser.eat(')', true); + } else if (name === 'global') { + error(parser.index, 'invalid-css-global-selector'); } children.push({ @@ -283,10 +294,23 @@ function read_selector(parser) { start, end: parser.index }); + } else if (parser.match_regex(REGEX_NTH_OF)) { + children.push({ + type: 'Nth', + value: /** @type {string} */ (parser.read(REGEX_NTH_OF)), + start, + end: parser.index + }); } else { + let name = read_identifier(parser); + if (parser.match('|')) { + // we ignore the namespace when trying to find matching element classes + parser.index++; + name = read_identifier(parser); + } children.push({ type: 'TypeSelector', - name: read_identifier(parser), + name, start, end: parser.index }); @@ -295,7 +319,7 @@ function read_selector(parser) { const index = parser.index; parser.allow_whitespace(); - if (parser.match('{') || parser.match(',')) { + if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) { parser.index = index; return { diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index a5e2a2a82..019e29c94 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -202,11 +202,13 @@ export default function tag(parser) { let attribute; while ((attribute = read(parser))) { - if ( - (attribute.type === 'Attribute' || attribute.type === 'BindDirective') && - unique_names.includes(attribute.name) - ) { - error(attribute.start, 'duplicate-attribute'); + if (attribute.type === 'Attribute' || attribute.type === 'BindDirective') { + if (unique_names.includes(attribute.name)) { + error(attribute.start, 'duplicate-attribute'); + // <svelte:element bind:this this=..> is allowed + } else if (attribute.name !== 'this') { + unique_names.push(attribute.name); + } } element.attributes.push(attribute); @@ -635,13 +637,14 @@ function read_attribute_value(parser) { 'in attribute value' ); } catch (/** @type {any} e */ e) { - if (e.code === 'parse-error') { + if (e.code === 'js-parse-error') { // if the attribute value didn't close + self-closing tag // eg: `<Component test={{a:1} />` // acorn may throw a `Unterminated regular expression` because of `/>` - if (parser.template.slice(e.pos - 1, e.pos + 1) === '/>') { - parser.index = e.pos; - error(e.pos, 'unclosed-attribute-value', quote_mark || '}'); + const pos = e.position?.[0]; + if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') { + parser.index = pos; + error(pos, 'unclosed-attribute-value', quote_mark || '}'); } } throw e; diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 2ad20d9cf..ebfebb73b 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -2,8 +2,8 @@ import read_context from '../read/context.js'; import read_expression from '../read/expression.js'; import { error } from '../../../errors.js'; import { create_fragment } from '../utils/create.js'; -import { parse_expression_at } from '../acorn.js'; import { walk } from 'zimmerframe'; +import { parse } from '../acorn.js'; const regex_whitespace_with_closing_curly_brace = /^\s*}/; @@ -315,7 +315,7 @@ function next(parser) { const block = parser.current(); // TODO type should not be TemplateNode, that's much too broad if (block.type === 'IfBlock') { - if (!parser.eat('else')) error(start, 'expected-token', 'else'); + if (!parser.eat('else')) error(start, 'expected-token', '{:else} or {:else if}'); if (parser.eat('if')) error(start, 'invalid-elseif'); parser.allow_whitespace(); @@ -359,7 +359,7 @@ function next(parser) { } if (block.type === 'EachBlock') { - if (!parser.eat('else')) error(start, 'expected-token', 'else'); + if (!parser.eat('else')) error(start, 'expected-token', '{:else}'); parser.allow_whitespace(); parser.eat('}', true); @@ -375,7 +375,7 @@ function next(parser) { if (block.type === 'AwaitBlock') { if (parser.eat('then')) { if (block.then) { - error(start, 'TODO', 'duplicate then'); + error(start, 'duplicate-block-part', '{:then}'); } if (!parser.eat('}')) { @@ -394,7 +394,7 @@ function next(parser) { if (parser.eat('catch')) { if (block.catch) { - error(start, 'TODO', 'duplicate catch'); + error(start, 'duplicate-block-part', '{:catch}'); } if (!parser.eat('}')) { @@ -413,6 +413,8 @@ function next(parser) { error(start, 'expected-token', '{:then ...} or {:catch ...}'); } + + error(start, 'invalid-continuing-block-placement'); } /** @param {import('../index.js').Parser} parser */ @@ -530,15 +532,41 @@ function special(parser) { if (parser.eat('const')) { // {@const a = b} + const start_index = parser.index - 5; parser.require_whitespace(); - const expression = read_expression(parser); + let end_index = parser.index; + /** @type {import('estree').VariableDeclaration | undefined} */ + let declaration = undefined; - if (!(expression.type === 'AssignmentExpression' && expression.operator === '=')) { + // Can't use parse_expression_at here, so we try to parse until we find the correct range + const dummy_spaces = parser.template.substring(0, start_index).replace(/[^\n]/g, ' '); + while (true) { + end_index = parser.template.indexOf('}', end_index + 1); + if (end_index === -1) break; + try { + const node = parse( + dummy_spaces + parser.template.substring(start_index, end_index), + parser.ts + ).body[0]; + if (node?.type === 'VariableDeclaration') { + declaration = node; + break; + } + } catch (e) { + continue; + } + } + + if ( + declaration === undefined || + declaration.declarations.length !== 1 || + declaration.declarations[0].init === undefined + ) { error(start, 'invalid-const'); } - parser.allow_whitespace(); + parser.index = end_index; parser.eat('}', true); parser.append( @@ -546,7 +574,7 @@ function special(parser) { type: 'ConstTag', start, end: parser.index, - expression + declaration }) ); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/a11y.js b/packages/svelte/src/compiler/phases/2-analyze/a11y.js index 33692ebaf..eb87b5ddb 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/a11y.js +++ b/packages/svelte/src/compiler/phases/2-analyze/a11y.js @@ -9,7 +9,7 @@ import { } from '../patterns.js'; import { warn } from '../../warnings.js'; import fuzzymatch from '../1-parse/utils/fuzzymatch.js'; -import { is_text_attribute } from '../../utils/ast.js'; +import { is_event_attribute, is_text_attribute } from '../../utils/ast.js'; import { ContentEditableBindings } from '../constants.js'; import { walk } from 'zimmerframe'; @@ -704,10 +704,14 @@ function check_element(node, state, path) { } else if (attribute.type === 'OnDirective') { handlers.add(attribute.name); } else if (attribute.type === 'Attribute') { - attributes.push(attribute); - attribute_map.set(attribute.name, attribute); - if (attribute.name === 'contenteditable') { - has_contenteditable_attr = true; + if (is_event_attribute(attribute)) { + handlers.add(attribute.name.slice(2)); + } else { + attributes.push(attribute); + attribute_map.set(attribute.name, attribute); + if (attribute.name === 'contenteditable') { + has_contenteditable_attr = true; + } } } else if ( attribute.type === 'BindDirective' && diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js index 25e44b4e4..b0942d657 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js @@ -17,7 +17,6 @@ const whitelist_attribute_selector = new Map([ ['details', new Set(['open'])], ['dialog', new Set(['open'])] ]); -const regex_is_single_css_selector = /[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/; export default class Selector { /** @type {import('#compiler').Css.Selector} */ @@ -157,11 +156,10 @@ export default class Selector { if ( selector.type === 'PseudoClassSelector' && selector.name === 'global' && - selector.args !== null + selector.args !== null && + selector.args.children.length > 1 ) { - if (regex_is_single_css_selector.test(selector.args)) { - error(selector, 'invalid-css-global-selector'); - } + error(selector, 'invalid-css-global-selector'); } } } @@ -179,11 +177,14 @@ export default class Selector { validate_global_compound_selector() { for (const block of this.blocks) { - for (const selector of block.selectors) { + for (let i = 0; i < block.selectors.length; i++) { + const selector = block.selectors[i]; if ( selector.type === 'PseudoClassSelector' && selector.name === 'global' && - block.selectors.length !== 1 + block.selectors.length !== 1 && + (i === block.selectors.length - 1 || + block.selectors.slice(i + 1).some((s) => s.type !== 'PseudoElementSelector')) ) { error(selector, 'invalid-css-global-selector-list'); } @@ -305,7 +306,7 @@ function block_might_apply_to_node(block, node) { while (i--) { const selector = block.selectors[i]; - if (selector.type === 'Percentage') continue; + if (selector.type === 'Percentage' || selector.type === 'Nth') continue; const name = selector.name.replace(regex_backslash_and_following_character, '$1'); diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 5e8ba056b..1be80cc16 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -5,10 +5,10 @@ import * as assert from '../../utils/assert.js'; import { extract_identifiers, extract_paths, - get_callee_name, is_event_attribute, is_text_attribute, - object + object, + unwrap_ts_expression } from '../../utils/ast.js'; import * as b from '../../utils/builders.js'; import { ReservedKeywords, Runes, SVGElements } from '../constants.js'; @@ -168,17 +168,23 @@ function get_delegated_event(node, context) { const scope = target_function.metadata.scope; for (const [reference] of scope.references) { + // Bail-out if the arguments keyword is used + if (reference === 'arguments') { + return non_hoistable; + } const binding = scope.get(reference); if ( binding !== null && - // Bail-out if we reference anything from the EachBlock (for now) that mutates in non-runes mode, - ((!context.state.analysis.runes && binding.kind === 'each') || - // or any normal not reactive bindings that are mutated. - binding.kind === 'normal' || - // or any reactive imports (those are rewritten) (can only happen in legacy mode) - (binding.kind === 'state' && binding.declaration_kind === 'import')) && - binding.mutated + // Bail-out if the the binding is a rest param + (binding.declaration_kind === 'rest_param' || + // Bail-out if we reference anything from the EachBlock (for now) that mutates in non-runes mode, + (((!context.state.analysis.runes && binding.kind === 'each') || + // or any normal not reactive bindings that are mutated. + binding.kind === 'normal' || + // or any reactive imports (those are rewritten) (can only happen in legacy mode) + binding.kind === 'legacy_reactive_import') && + binding.mutated)) ) { return non_hoistable; } @@ -201,30 +207,27 @@ export function analyze_module(ast, options) { } } + /** @type {import('../types').RawWarning[]} */ + const warnings = []; + + const analysis = { + warnings + }; + walk( /** @type {import('estree').Node} */ (ast), - { scope }, + { scope, analysis }, // @ts-expect-error TODO clean this mess up merge(set_scope(scopes), validation_runes_js, runes_scope_js_tweaker) ); - /** @type {import('../types').RawWarning[]} */ - const warnings = []; - - // If we are in runes mode, then check for possible misuses of state runes - for (const [, scope] of scopes) { - for (const [name, binding] of scope.declarations) { - if (binding.kind === 'state' && !binding.mutated) { - warn(warnings, binding.node, [], 'state-rune-not-mutated', name); - } - } - } - return { module: { ast, scope, scopes }, name: options.filename || 'module', warnings, - accessors: false + accessors: false, + runes: true, + immutable: true }; } @@ -261,14 +264,10 @@ export function analyze_component(root, options) { // is referencing a rune and not a global store. if ( options.runes === false || - !Runes.includes(name) || + !Runes.includes(/** @type {any} */ (name)) || (declaration !== null && // const state = $state(0) is valid - !Runes.includes( - /** @type {string} */ ( - get_callee_name(/** @type {import('estree').Expression} */ (declaration.initial)) - ) - ) && + get_rune(declaration.initial, instance.scope) === null && // allow `import { derived } from 'svelte/store'` in the same file as `const x = $derived(..)` because one is not a subscription to the other !( name === '$derived' && @@ -279,8 +278,12 @@ export function analyze_component(root, options) { if (options.runes !== false) { if (declaration === null && /[a-z]/.test(store_name[0])) { error(references[0].node, 'illegal-global', name); - } else if (declaration !== null && Runes.includes(name)) { - warn(warnings, declaration.node, [], 'store-with-rune-name', store_name); + } else if (declaration !== null && Runes.includes(/** @type {any} */ (name))) { + for (const { node, path } of references) { + if (path.at(-1)?.type === 'CallExpression') { + warn(warnings, node, [], 'store-with-rune-name', store_name); + } + } } } @@ -298,11 +301,17 @@ export function analyze_component(root, options) { const binding = instance.scope.declare(b.id(name), 'store_sub', 'synthetic'); binding.references = references; + instance.scope.references.set(name, references); + module.scope.references.delete(name); } } const component_name = get_component_name(options.filename ?? 'Component'); + const runes = + options.runes ?? + Array.from(module.scope.references).some(([name]) => Runes.includes(/** @type {any} */ (name))); + // TODO remove all the ?? stuff, we don't need it now that we're validating the config /** @type {import('../types.js').ComponentAnalysis} */ const analysis = { @@ -319,8 +328,8 @@ export function analyze_component(root, options) { component_name, get_css_hash: options.cssHash }), - runes: - options.runes ?? Array.from(module.scope.references).some(([name]) => Runes.includes(name)), + runes, + immutable: runes || options.immutable, exports: [], uses_props: false, uses_rest_props: false, @@ -367,15 +376,6 @@ export function analyze_component(root, options) { merge(set_scope(scopes), validation_runes, runes_scope_tweaker, common_visitors) ); } - - // If we are in runes mode, then check for possible misuses of state runes - for (const [, scope] of instance.scopes) { - for (const [name, binding] of scope.declarations) { - if (binding.kind === 'state' && !binding.mutated) { - warn(warnings, binding.node, [], 'state-rune-not-mutated', name); - } - } - } } else { instance.scope.declare(b.id('$$props'), 'prop', 'synthetic'); instance.scope.declare(b.id('$$restProps'), 'rest_prop', 'synthetic'); @@ -409,6 +409,30 @@ export function analyze_component(root, options) { analysis.reactive_statements = order_reactive_statements(analysis.reactive_statements); } + // warn on any nonstate declarations that are a) mutated and b) referenced in the template + for (const scope of [module.scope, instance.scope]) { + outer: for (const [name, binding] of scope.declarations) { + if (binding.kind === 'normal' && binding.mutated) { + for (const { path } of binding.references) { + if (path[0].type !== 'Fragment') continue; + for (let i = 1; i < path.length; i += 1) { + const type = path[i].type; + if ( + type === 'FunctionDeclaration' || + type === 'FunctionExpression' || + type === 'ArrowFunctionExpression' + ) { + continue; + } + } + + warn(warnings, binding.node, [], 'non-state-reference', name); + continue outer; + } + } + } + } + analysis.stylesheet.validate(analysis); for (const element of analysis.elements) { @@ -514,7 +538,7 @@ const legacy_scope_tweaker = { (state.reactive_statement || state.ast_type === 'template') && parent.type === 'MemberExpression' ) { - binding.kind = 'state'; + binding.kind = 'legacy_reactive_import'; } } else if ( binding.mutated && @@ -608,7 +632,7 @@ const legacy_scope_tweaker = { } }; -/** @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, { scope: Scope }>} */ +/** @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, { scope: Scope, analysis: { warnings: import('../types').RawWarning[] } }>} */ const runes_scope_js_tweaker = { VariableDeclarator(node, { state }) { if (node.init?.type !== 'CallExpression') return; @@ -630,11 +654,20 @@ const runes_scope_js_tweaker = { /** @type {import('./types').Visitors} */ const runes_scope_tweaker = { + CallExpression(node, { state, next }) { + const rune = get_rune(node, state.scope); + + // `$inspect(foo)` should not trigger the `static-state-reference` warning + if (rune === '$inspect') { + next({ ...state, function_depth: state.function_depth + 1 }); + } + }, VariableDeclarator(node, { state }) { - if (node.init?.type !== 'CallExpression') return; - if (get_rune(node.init, state.scope) === null) return; + const init = unwrap_ts_expression(node.init); + if (!init || init.type !== 'CallExpression') return; + if (get_rune(init, state.scope) === null) return; - const callee = node.init.callee; + const callee = init.callee; if (callee.type !== 'Identifier') return; const name = callee.name; @@ -849,6 +882,12 @@ const common_visitors = { Identifier(node, context) { const parent = /** @type {import('estree').Node} */ (context.path.at(-1)); if (!is_reference(node, parent)) return; + + if (node.name === '$$slots') { + context.state.analysis.uses_slots = true; + return; + } + const binding = context.state.scope.get(node.name); // if no binding, means some global variable @@ -1063,7 +1102,8 @@ function determine_element_spread_and_delegatable(node) { has_spread = true; } else if ( !has_action_or_bind && - (attribute.type === 'BindDirective' || attribute.type === 'UseDirective') + ((attribute.type === 'BindDirective' && attribute.name !== 'this') || + attribute.type === 'UseDirective') ) { has_action_or_bind = true; } diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 2cf0129c5..3be08c75f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -1,11 +1,20 @@ import { error } from '../../errors.js'; -import { extract_identifiers, is_text_attribute } from '../../utils/ast.js'; +import { + extract_identifiers, + get_parent, + is_text_attribute, + unwrap_ts_expression +} from '../../utils/ast.js'; import { warn } from '../../warnings.js'; import fuzzymatch from '../1-parse/utils/fuzzymatch.js'; import { binding_properties } from '../bindings.js'; -import { SVGElements } from '../constants.js'; +import { ContentEditableBindings, EventModifiers, SVGElements } from '../constants.js'; import { is_custom_element_node } from '../nodes.js'; -import { regex_not_whitespace, regex_only_whitespaces } from '../patterns.js'; +import { + regex_illegal_attribute_character, + regex_not_whitespace, + regex_only_whitespaces +} from '../patterns.js'; import { Scope, get_rune } from '../scope.js'; import { merge } from '../visitors.js'; import { a11y_validators } from './a11y.js'; @@ -25,6 +34,13 @@ function validate_component(node, context) { ) { error(attribute, 'invalid-component-directive'); } + + if ( + attribute.type === 'OnDirective' && + (attribute.modifiers.length > 1 || attribute.modifiers.some((m) => m !== 'once')) + ) { + error(attribute, 'invalid-event-modifier'); + } } context.next({ @@ -39,17 +55,91 @@ function validate_component(node, context) { * @param {import('zimmerframe').Context<import('#compiler').SvelteNode, import('./types.js').AnalysisState>} context */ function validate_element(node, context) { + let has_animate_directive = false; + let has_in_transition = false; + let has_out_transition = false; + for (const attribute of node.attributes) { - if ( - attribute.type === 'Attribute' && - attribute.name === 'is' && - context.state.options.namespace !== 'foreign' - ) { - warn(context.state.analysis.warnings, attribute, context.path, 'avoid-is'); - } - if (attribute.type === 'Attribute' && attribute.name === 'slot') { - /** @type {import('#compiler').RegularElement | import('#compiler').SvelteElement | import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf | undefined} */ - validate_slot_attribute(context, attribute); + if (attribute.type === 'Attribute') { + if (regex_illegal_attribute_character.test(attribute.name)) { + error(attribute, 'invalid-attribute-name', attribute.name); + } + + if (attribute.name.startsWith('on') && attribute.name.length > 2) { + if ( + attribute.value === true || + is_text_attribute(attribute) || + attribute.value.length > 1 + ) { + error(attribute, 'invalid-event-attribute-value'); + } + } + + if (attribute.name === 'slot') { + /** @type {import('#compiler').RegularElement | import('#compiler').SvelteElement | import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf | undefined} */ + validate_slot_attribute(context, attribute); + } + + if (attribute.name === 'is' && context.state.options.namespace !== 'foreign') { + warn(context.state.analysis.warnings, attribute, context.path, 'avoid-is'); + } + } else if (attribute.type === 'AnimateDirective') { + const parent = context.path.at(-2); + if (parent?.type !== 'EachBlock') { + error(attribute, 'invalid-animation', 'no-each'); + } else if (!parent.key) { + error(attribute, 'invalid-animation', 'each-key'); + } else if ( + parent.body.nodes.filter( + (n) => + n.type !== 'Comment' && + n.type !== 'ConstTag' && + (n.type !== 'Text' || n.data.trim() !== '') + ).length > 1 + ) { + error(attribute, 'invalid-animation', 'child'); + } + + if (has_animate_directive) { + error(attribute, 'duplicate-animation'); + } else { + has_animate_directive = true; + } + } else if (attribute.type === 'TransitionDirective') { + if ((attribute.outro && has_out_transition) || (attribute.intro && has_in_transition)) { + /** @param {boolean} _in @param {boolean} _out */ + const type = (_in, _out) => (_in && _out ? 'transition' : _in ? 'in' : 'out'); + error( + attribute, + 'duplicate-transition', + type(has_in_transition, has_out_transition), + type(attribute.intro, attribute.outro) + ); + } + + has_in_transition = has_in_transition || attribute.intro; + has_out_transition = has_out_transition || attribute.outro; + } else if (attribute.type === 'OnDirective') { + let has_passive_modifier = false; + let conflicting_passive_modifier = ''; + for (const modifier of attribute.modifiers) { + if (!EventModifiers.includes(modifier)) { + error(attribute, 'invalid-event-modifier', EventModifiers); + } + if (modifier === 'passive') { + has_passive_modifier = true; + } else if (modifier === 'nonpassive' || modifier === 'preventDefault') { + conflicting_passive_modifier = modifier; + } + if (has_passive_modifier && conflicting_passive_modifier) { + error( + attribute, + 'invalid-event-modifier-combination', + 'passive', + conflicting_passive_modifier + ); + } + } } } } @@ -238,13 +328,6 @@ function is_tag_valid_with_parent(tag, parent_tag) { * @type {import('zimmerframe').Visitors<import('#compiler').SvelteNode, import('./types.js').AnalysisState>} */ export const validation = { - Attribute(node) { - if (node.name.startsWith('on') && node.name.length > 2) { - if (node.value === true || is_text_attribute(node) || node.value.length > 1) { - error(node, 'invalid-event-attribute-value'); - } - } - }, BindDirective(node, context) { validate_no_const_assignment(node, node.expression, context.state.scope, true); @@ -318,7 +401,7 @@ export const validation = { ); } - if (parent.name === 'input') { + if (parent.name === 'input' && node.name !== 'this') { const type = /** @type {import('#compiler').Attribute | undefined} */ ( parent.attributes.find((a) => a.type === 'Attribute' && a.name === 'type') ); @@ -356,6 +439,17 @@ export const validation = { `non-<svg> elements. Use 'clientWidth' for <svg> instead` ); } + + if (ContentEditableBindings.includes(node.name)) { + const contenteditable = /** @type {import('#compiler').Attribute} */ ( + parent.attributes.find((a) => a.type === 'Attribute' && a.name === 'contenteditable') + ); + if (!contenteditable) { + error(node, 'missing-contenteditable-attribute'); + } else if (!is_text_attribute(contenteditable)) { + error(contenteditable, 'dynamic-contenteditable-attribute'); + } + } } else { const match = fuzzymatch(node.name, Object.keys(binding_properties)); if (match) { @@ -368,6 +462,40 @@ export const validation = { } } }, + ExportDefaultDeclaration(node) { + error(node, 'default-export'); + }, + ConstTag(node, context) { + const parent = context.path.at(-1); + const grand_parent = context.path.at(-2); + if ( + parent?.type !== 'Fragment' || + (grand_parent?.type !== 'IfBlock' && + grand_parent?.type !== 'SvelteFragment' && + grand_parent?.type !== 'Component' && + grand_parent?.type !== 'SvelteComponent' && + grand_parent?.type !== 'EachBlock' && + grand_parent?.type !== 'AwaitBlock' && + ((grand_parent?.type !== 'RegularElement' && grand_parent?.type !== 'SvelteElement') || + !grand_parent.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot'))) + ) { + error(node, 'invalid-const-placement'); + } + }, + LetDirective(node, context) { + const parent = context.path.at(-1); + if ( + parent === undefined || + (parent.type !== 'Component' && + parent.type !== 'RegularElement' && + parent.type !== 'SvelteElement' && + parent.type !== 'SvelteComponent' && + parent.type !== 'SvelteSelf' && + parent.type !== 'SvelteFragment') + ) { + error(node, 'invalid-let-directive-placement'); + } + }, RegularElement(node, context) { if (node.name === 'textarea' && node.fragment.nodes.length > 0) { for (const attribute of node.attributes) { @@ -377,6 +505,21 @@ export const validation = { } } + const binding = context.state.scope.get(node.name); + if ( + binding !== null && + binding.declaration_kind === 'import' && + binding.references.length === 0 + ) { + warn( + context.state.analysis.warnings, + node, + context.path, + 'component-name-lowercase', + node.name + ); + } + validate_element(node, context); if (context.state.parent_element) { @@ -390,6 +533,12 @@ export const validation = { parent_element: node.name }); }, + SvelteHead(node) { + const attribute = node.attributes[0]; + if (attribute) { + error(attribute, 'illegal-svelte-head-attribute'); + } + }, SvelteElement(node, context) { validate_element(node, context); context.next({ @@ -398,6 +547,11 @@ export const validation = { }); }, SvelteFragment(node, context) { + const parent = context.path.at(-2); + if (parent?.type !== 'Component' && parent?.type !== 'SvelteComponent') { + error(node, 'invalid-svelte-fragment-placement'); + } + for (const attribute of node.attributes) { if (attribute.type === 'Attribute') { if (attribute.name === 'slot') { @@ -413,7 +567,11 @@ export const validation = { if (attribute.type === 'Attribute') { if (attribute.name === 'name') { if (!is_text_attribute(attribute)) { - error(attribute, 'invalid-slot-name'); + error(attribute, 'invalid-slot-name', false); + } + const slot_name = attribute.value[0].data; + if (slot_name === 'default') { + error(attribute, 'invalid-slot-name', true); } } } else if (attribute.type !== 'SpreadAttribute') { @@ -432,6 +590,17 @@ export const validation = { } } }, + TitleElement(node) { + const attribute = node.attributes[0]; + if (attribute) { + error(attribute, 'illegal-title-attribute'); + } + + const child = node.fragment.nodes.find((n) => n.type !== 'Text' && n.type !== 'ExpressionTag'); + if (child) { + error(child, 'invalid-title-content'); + } + }, ExpressionTag(node, context) { if (!node.parent) return; if (context.state.parent_element) { @@ -443,7 +612,7 @@ export const validation = { }; export const validation_legacy = merge(validation, a11y_validators, { - VariableDeclarator(node) { + VariableDeclarator(node, { state }) { if (node.init?.type !== 'CallExpression') return; const callee = node.init.callee; @@ -454,14 +623,24 @@ export const validation_legacy = merge(validation, a11y_validators, { return; } - // TODO check if it's a store subscription that's called? How likely is it that someone uses a store that contains a function? - error(node.init, 'invalid-rune-usage', callee.name); + if (state.scope.get(callee.name)?.kind !== 'store_sub') { + error(node.init, 'invalid-rune-usage', callee.name); + } }, AssignmentExpression(node, { state, path }) { const parent = path.at(-1); if (parent && parent.type === 'ConstTag') return; validate_assignment(node, node.left, state); }, + LabeledStatement(node, { path, state }) { + if ( + node.label.name === '$' && + (state.ast_type !== 'instance' || + /** @type {import('#compiler').SvelteNode} */ (path.at(-1)).type !== 'Program') + ) { + warn(state.analysis.warnings, node, path, 'no-reactive-declaration'); + } + }, UpdateExpression(node, { state }) { validate_assignment(node, node.argument, state); } @@ -475,8 +654,14 @@ export const validation_legacy = merge(validation, a11y_validators, { */ function validate_export(node, scope, name) { const binding = scope.get(name); - if (binding && (binding.kind === 'derived' || binding.kind === 'state')) { - error(node, 'invalid-rune-export', `$${binding.kind}`); + if (!binding) return; + + if (binding.kind === 'derived') { + error(node, 'invalid-derived-export'); + } + + if (binding.kind === 'state' && binding.reassigned) { + error(node, 'invalid-state-export'); } } @@ -490,7 +675,7 @@ function validate_call_expression(node, scope, path) { const rune = get_rune(node, scope); if (rune === null) return; - const parent = /** @type {import('#compiler').SvelteNode} */ (path.at(-1)); + const parent = /** @type {import('#compiler').SvelteNode} */ (get_parent(path, -1)); if (rune === '$props') { if (parent.type === 'VariableDeclarator') return; @@ -503,13 +688,31 @@ function validate_call_expression(node, scope, path) { error(node, rune === '$derived' ? 'invalid-derived-location' : 'invalid-state-location'); } - if (rune === '$effect') { + if (rune === '$effect' || rune === '$effect.pre') { if (parent.type !== 'ExpressionStatement') { error(node, 'invalid-effect-location'); } if (node.arguments.length !== 1) { - error(node, 'invalid-rune-args-length', '$effect', [1]); + error(node, 'invalid-rune-args-length', rune, [1]); + } + } + + if (rune === '$effect.active') { + if (node.arguments.length !== 0) { + error(node, 'invalid-rune-args-length', rune, [0]); + } + } + + if (rune === '$effect.root') { + if (node.arguments.length !== 1) { + error(node, 'invalid-rune-args-length', rune, [1]); + } + } + + if (rune === '$inspect') { + if (node.arguments.length < 1 || node.arguments.length > 2) { + error(node, 'invalid-rune-args-length', rune, [1, 2]); } } } @@ -579,6 +782,21 @@ export const validation_runes_js = { ...context.state, private_derived_state }); + }, + ClassDeclaration(node, context) { + // In modules, we allow top-level module scope only, in components, we allow the component scope, + // which is function_depth of 1. With the exception of `new class` which is also not allowed at + // component scope level either. + const allowed_depth = context.state.ast_type === 'module' ? 0 : 1; + + if (context.state.scope.function_depth > allowed_depth) { + warn(context.state.analysis.warnings, node, context.path, 'avoid-nested-class'); + } + }, + NewExpression(node, context) { + if (node.callee.type === 'ClassExpression' && context.state.scope.function_depth > 0) { + warn(context.state.analysis.warnings, node, context.path, 'avoid-inline-class'); + } } }; @@ -589,7 +807,19 @@ export const validation_runes_js = { * @param {boolean} is_binding */ function validate_no_const_assignment(node, argument, scope, is_binding) { - if (argument.type === 'Identifier') { + if (argument.type === 'ArrayPattern') { + for (const element of argument.elements) { + if (element) { + validate_no_const_assignment(node, element, scope, is_binding); + } + } + } else if (argument.type === 'ObjectPattern') { + for (const element of argument.properties) { + if (element.type === 'Property') { + validate_no_const_assignment(node, element.value, scope, is_binding); + } + } + } else if (argument.type === 'Identifier') { const binding = scope.get(argument.name); if (binding?.declaration_kind === 'const' && binding.kind !== 'each') { error( @@ -675,7 +905,7 @@ export const validation_runes = merge(validation, a11y_validators, { next({ ...state }); }, VariableDeclarator(node, { state }) { - const init = node.init; + const init = unwrap_ts_expression(node.init); const rune = get_rune(init, state.scope); if (rune === null) return; @@ -721,5 +951,8 @@ export const validation_runes = merge(validation, a11y_validators, { } } }, - ClassBody: validation_runes_js.ClassBody + // TODO this is a code smell. need to refactor this stuff + ClassBody: validation_runes_js.ClassBody, + ClassDeclaration: validation_runes_js.ClassDeclaration, + NewExpression: validation_runes_js.NewExpression }); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 1afe2058b..d69b70ec0 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -159,7 +159,7 @@ export function client_component(source, analysis, options) { // Very very dirty way of making import statements reactive in legacy mode if needed if (!analysis.runes) { for (const [name, binding] of analysis.module.scope.declarations) { - if (binding.kind === 'state' && binding.declaration_kind === 'import') { + if (binding.kind === 'legacy_reactive_import') { instance.body.unshift( b.var('$$_import_' + name, b.call('$.reactive_import', b.thunk(b.id(name)))) ); @@ -175,7 +175,7 @@ export function client_component(source, analysis, options) { for (const [name, binding] of analysis.instance.scope.declarations) { if (binding.kind === 'legacy_reactive') { - legacy_reactive_declarations.push(b.const(name, b.call('$.source'))); + legacy_reactive_declarations.push(b.const(name, b.call('$.mutable_source'))); } if (binding.kind === 'store_sub') { if (store_setup.length === 0) { @@ -240,11 +240,12 @@ export function client_component(source, analysis, options) { const properties = analysis.exports.map(({ name, alias }) => { const binding = analysis.instance.scope.get(name); + const is_source = + binding?.kind === 'state' && (!state.analysis.immutable || binding.reassigned); + // TODO This is always a getter because the `renamed-instance-exports` test wants it that way. // Should we for code size reasons make it an init in runes mode and/or non-dev mode? - return b.get(alias ?? name, [ - b.return(binding?.kind === 'state' ? b.call('$.get', b.id(name)) : b.id(name)) - ]); + return b.get(alias ?? name, [b.return(is_source ? b.call('$.get', b.id(name)) : b.id(name))]); }); if (analysis.accessors) { @@ -261,14 +262,7 @@ export function client_component(source, analysis, options) { } const component_block = b.block([ - b.stmt( - b.call( - '$.push', - b.id('$$props'), - b.literal(analysis.runes), - ...(options.immutable ? [b.literal(true)] : []) - ) - ), + b.stmt(b.call('$.push', b.id('$$props'), b.literal(analysis.runes))), ...store_setup, ...legacy_reactive_declarations, ...group_binding_declarations, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 1194cffcd..d6700add1 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -1,5 +1,5 @@ import * as b from '../../../utils/builders.js'; -import { extract_paths } from '../../../utils/ast.js'; +import { extract_paths, is_simple_expression } from '../../../utils/ast.js'; import { error } from '../../../errors.js'; /** @@ -67,19 +67,20 @@ export function serialize_get_binding(node, state) { if ( binding.kind === 'prop' && - !binding.mutated && + !(state.analysis.immutable ? binding.reassigned : binding.mutated) && !binding.initial && !state.analysis.accessors ) { return b.call(node); } - if (binding.kind === 'state' && binding.declaration_kind === 'import') { + if (binding.kind === 'legacy_reactive_import') { return b.call('$$_import_' + node.name); } if ( - binding.kind === 'state' || + (binding.kind === 'state' && + (!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) || binding.kind === 'derived' || binding.kind === 'prop' || binding.kind === 'rest_prop' || @@ -99,7 +100,7 @@ export function serialize_get_binding(node, state) { * @returns {import('estree').Expression} */ export function serialize_set_binding(node, context, fallback) { - const { state, visit, path } = context; + const { state, visit } = context; if ( node.left.type === 'ArrayPattern' || @@ -152,7 +153,11 @@ export function serialize_set_binding(node, context, fallback) { if (left.object.type === 'ThisExpression' && left.property.type === 'PrivateIdentifier') { if (context.state.private_state.has(left.property.name) && !state.in_constructor) { const value = get_assignment_value(node, context); - return b.call('$.set', left, value); + return b.call( + '$.set', + left, + context.state.analysis.runes && should_proxy(value) ? b.call('$.proxy', value) : value + ); } } // @ts-expect-error @@ -171,7 +176,7 @@ export function serialize_set_binding(node, context, fallback) { return binding.mutation(node, context); } - if (binding.kind === 'state' && binding.declaration_kind === 'import') { + if (binding.kind === 'legacy_reactive_import') { return b.call( '$$_import_' + binding.node.name, b.assignment( @@ -202,7 +207,11 @@ export function serialize_set_binding(node, context, fallback) { if (is_store) { return b.call('$.store_set', serialize_get_binding(b.id(left_name), state), value); } else { - return b.call('$.set', b.id(left_name), value); + return b.call( + '$.set', + b.id(left_name), + context.state.analysis.runes && should_proxy(value) ? b.call('$.proxy', value) : value + ); } } else { if (is_store) { @@ -216,7 +225,7 @@ export function serialize_set_binding(node, context, fallback) { ), b.call('$' + left_name) ); - } else { + } else if (!state.analysis.runes) { return b.call( '$.mutate', b.id(left_name), @@ -226,6 +235,12 @@ export function serialize_set_binding(node, context, fallback) { value ) ); + } else { + return b.assignment( + node.operator, + /** @type {import('estree').Pattern} */ (visit(node.left)), + /** @type {import('estree').Expression} */ (visit(node.right)) + ); } } }; @@ -288,7 +303,8 @@ function get_hoistable_params(node, context) { params.push(b.id(binding.node.name.slice(1))); params.push(b.id(binding.node.name)); } else { - params.push(binding.node); + // create a copy to remove start/end tags which would mess up source maps + params.push(b.id(binding.node.name)); } } } @@ -334,24 +350,38 @@ export function get_props_method(binding, state, name, default_value) { /** @type {import('estree').Expression[]} */ const args = [b.id('$$props'), b.literal(name)]; + // Use $.prop_source in the following cases: + // - accessors/mutated: needs to be able to set the prop value from within + // - default value: we set the fallback value only initially, and it's not possible to know this timing in $.prop + const needs_source = + default_value || + state.analysis.accessors || + (state.analysis.immutable ? binding.reassigned : binding.mutated); + + if (needs_source) { + args.push(b.literal(state.analysis.immutable)); + } + if (default_value) { // To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary - if (default_value.type !== 'Literal' && default_value.type !== 'Identifier') { - args.push(b.thunk(default_value)); - args.push(b.true); - } else { + if (is_simple_expression(default_value)) { args.push(default_value); - args.push(b.false); + } else { + if ( + default_value.type === 'CallExpression' && + default_value.callee.type === 'Identifier' && + default_value.arguments.length === 0 + ) { + args.push(default_value.callee); + } else { + args.push(b.thunk(default_value)); + } + + args.push(b.true); } } - return b.call( - // Use $.prop_source in the following cases: - // - accessors/mutated: needs to be able to set the prop value from within - // - default value: we set the fallback value only initially, and it's not possible to know this timing in $.prop - binding.mutated || binding.initial || state.analysis.accessors ? '$.prop_source' : '$.prop', - ...args - ); + return b.call(needs_source ? '$.prop_source' : '$.prop', ...args); } /** @@ -363,7 +393,7 @@ export function get_props_method(binding, state, name, default_value) { export function create_state_declarators(declarator, scope, value) { // in the simple `let count = $state(0)` case, we rewrite `$state` as `$.source` if (declarator.id.type === 'Identifier') { - return [b.declarator(declarator.id, b.call('$.source', value))]; + return [b.declarator(declarator.id, b.call('$.mutable_source', value))]; } const tmp = scope.generate('tmp'); @@ -373,7 +403,25 @@ export function create_state_declarators(declarator, scope, value) { ...paths.map((path) => { const value = path.expression?.(b.id(tmp)); const binding = scope.get(/** @type {import('estree').Identifier} */ (path.node).name); - return b.declarator(path.node, binding?.kind === 'state' ? b.call('$.source', value) : value); + return b.declarator( + path.node, + binding?.kind === 'state' ? b.call('$.mutable_source', value) : value + ); }) ]; } + +/** @param {import('estree').Expression} node */ +export function should_proxy(node) { + if ( + !node || + node.type === 'Literal' || + node.type === 'ArrowFunctionExpression' || + node.type === 'FunctionExpression' || + (node.type === 'Identifier' && node.name === 'undefined') + ) { + return false; + } + + return true; +} diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js index 218756ad8..bfc7998b8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js @@ -104,15 +104,16 @@ export const javascript_visitors_legacy = { if (context.path.length > 1) return; if (node.label.name !== '$') return; const state = context.state; - - // TODO bail out if we're in module context - // To recreate Svelte 4 behaviour, we track the dependencies // the compiler can 'see', but we untrack the effect itself - const { dependencies } = /** @type {import('#compiler').ReactiveStatement} */ ( + const reactive_stmt = /** @type {import('#compiler').ReactiveStatement} */ ( state.analysis.reactive_statements.get(node) ); + if (!reactive_stmt) return; // not the instance context + + const { dependencies } = reactive_stmt; + let serialized_body = /** @type {import('estree').Statement} */ (context.visit(node.body)); if (serialized_body.type !== 'BlockStatement') { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js index b59526c94..01a6ec7df 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js @@ -2,7 +2,8 @@ import { get_rune } from '../../../scope.js'; import { is_hoistable_function } from '../../utils.js'; import * as b from '../../../../utils/builders.js'; import * as assert from '../../../../utils/assert.js'; -import { create_state_declarators, get_props_method } from '../utils.js'; +import { create_state_declarators, get_props_method, should_proxy } from '../utils.js'; +import { unwrap_ts_expression } from '../../../../utils/ast.js'; /** @type {import('../types.js').ComponentVisitors} */ export const javascript_visitors_runes = { @@ -83,7 +84,7 @@ export const javascript_visitors_runes = { value = field.kind === 'state' - ? b.call('$.source', init) + ? b.call('$.source', should_proxy(init) ? b.call('$.proxy', init) : init) : b.call('$.derived', b.thunk(init)); } else { // if no arguments, we know it's state as `$derived()` is a compile error @@ -133,9 +134,9 @@ export const javascript_visitors_runes = { const declarations = []; for (const declarator of node.declarations) { - const init = declarator.init; + const init = unwrap_ts_expression(declarator.init); const rune = get_rune(init, state.scope); - if (!rune) { + if (!rune || rune === '$effect.active' || rune === '$effect.root' || rune === '$inspect') { if (init != null && is_hoistable_function(init)) { const hoistable_function = visit(init); state.hoisted.push( @@ -209,17 +210,29 @@ export const javascript_visitors_runes = { continue; } - const args = /** @type {import('estree').CallExpression} */ (declarator.init).arguments; - const value = + const args = /** @type {import('estree').CallExpression} */ (init).arguments; + let value = args.length === 0 ? b.id('undefined') : /** @type {import('estree').Expression} */ (visit(args[0])); - const opts = args[1] && /** @type {import('estree').Expression} */ (visit(args[1])); if (declarator.id.type === 'Identifier') { - const callee = rune === '$state' ? '$.source' : '$.derived'; - const arg = rune === '$state' ? value : b.thunk(value); - declarations.push(b.declarator(declarator.id, b.call(callee, arg, opts))); + if (rune === '$state') { + const binding = /** @type {import('#compiler').Binding} */ ( + state.scope.get(declarator.id.name) + ); + if (should_proxy(value)) { + value = b.call('$.proxy', value); + } + + if (!state.analysis.immutable || state.analysis.accessors || binding.reassigned) { + value = b.call('$.source', value); + } + } else { + value = b.call('$.derived', b.thunk(value)); + } + + declarations.push(b.declarator(declarator.id, value)); continue; } @@ -291,5 +304,34 @@ export const javascript_visitors_runes = { } context.next(); + }, + CallExpression(node, { state, next, visit }) { + const rune = get_rune(node, state.scope); + + if (rune === '$effect.active') { + return b.call('$.effect_active'); + } + + if (rune === '$effect.root') { + const args = /** @type {import('estree').Expression[]} */ ( + node.arguments.map((arg) => visit(arg)) + ); + return b.call('$.user_root_effect', ...args); + } + + if (rune === '$inspect') { + if (state.options.dev) { + const arg = /** @type {import('estree').Expression} */ (visit(node.arguments[0])); + const fn = + node.arguments[1] && + /** @type {import('estree').Expression} */ (visit(node.arguments[1])); + + return b.call('$.inspect', b.thunk(arg), fn); + } + + return b.unary('void', b.literal(0)); + } + + next(); } }; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 09adde9d2..feb917418 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -12,13 +12,7 @@ import { escape_html, infer_namespace } from '../../utils.js'; -import { - AttributeAliases, - DOMBooleanAttributes, - DOMProperties, - PassiveEvents, - VoidElements -} from '../../../constants.js'; +import { DOMProperties, PassiveEvents, VoidElements } from '../../../constants.js'; import { is_custom_element_node, is_element_node } from '../../../nodes.js'; import * as b from '../../../../utils/builders.js'; import { error } from '../../../../errors.js'; @@ -29,12 +23,36 @@ import { serialize_set_binding } from '../utils.js'; import { + AttributeAliases, + DOMBooleanAttributes, EACH_INDEX_REACTIVE, EACH_IS_CONTROLLED, + EACH_IS_IMMUTABLE, EACH_ITEM_REACTIVE, EACH_KEYED } from '../../../../../constants.js'; import { regex_is_valid_identifier } from '../../../patterns.js'; +import { javascript_visitors_runes } from './javascript-runes.js'; + +/** + * @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} element + * @param {import('#compiler').Attribute} attribute + * @param {{ state: { metadata: { namespace: import('#compiler').Namespace }}}} context + */ +function get_attribute_name(element, attribute, context) { + let name = attribute.name; + if ( + element.type === 'RegularElement' && + !element.metadata.svg && + context.state.metadata.namespace !== 'foreign' + ) { + name = name.toLowerCase(); + if (name in AttributeAliases) { + name = AttributeAliases[name]; + } + } + return name; +} /** * Serializes each style directive into something like `$.style(element, style_property, value)` @@ -258,10 +276,11 @@ function setup_select_synchronization(value_binding, context) { * Returns the id of the spread_attribute variable if spread is deemed reactive, `null` otherwise. * @param {Array<import('#compiler').Attribute | import('#compiler').SpreadAttribute>} attributes * @param {import('../types.js').ComponentContext} context + * @param {import('#compiler').RegularElement} element * @param {import('estree').Identifier} element_id * @returns {string | null} */ -function serialize_element_spread_attributes(attributes, context, element_id) { +function serialize_element_spread_attributes(attributes, context, element, element_id) { let is_reactive = false; /** @type {import('estree').Expression[]} */ @@ -269,7 +288,7 @@ function serialize_element_spread_attributes(attributes, context, element_id) { for (const attribute of attributes) { if (attribute.type === 'Attribute') { - const name = get_attribute_name(attribute, context.state); + const name = get_attribute_name(element, attribute, context); // TODO: handle contains_call_expression const [, value] = serialize_attribute_value(attribute.value, context); values.push(b.object([b.init(name, value)])); @@ -280,6 +299,9 @@ function serialize_element_spread_attributes(attributes, context, element_id) { is_reactive ||= attribute.metadata.dynamic; } + const lowercase_attributes = + element.metadata.svg || is_custom_element_node(element) ? b.false : b.true; + if (is_reactive) { const id = context.state.scope.generate('spread_attributes'); context.state.init.push(b.let(id, undefined)); @@ -293,6 +315,7 @@ function serialize_element_spread_attributes(attributes, context, element_id) { element_id, b.id(id), b.array(values), + lowercase_attributes, b.literal(context.state.analysis.stylesheet.id) ) ) @@ -307,6 +330,7 @@ function serialize_element_spread_attributes(attributes, context, element_id) { element_id, b.literal(null), b.array(values), + lowercase_attributes, b.literal(context.state.analysis.stylesheet.id) ) ) @@ -324,6 +348,15 @@ function serialize_element_spread_attributes(attributes, context, element_id) { * @returns {boolean} */ function serialize_dynamic_element_spread_attributes(attributes, context, element_id) { + if (attributes.length === 0) { + if (context.state.analysis.stylesheet.id) { + context.state.init.push( + b.stmt(b.call('$.class_name', element_id, b.literal(context.state.analysis.stylesheet.id))) + ); + } + return false; + } + let is_reactive = false; /** @type {import('estree').Expression[]} */ @@ -397,14 +430,15 @@ function serialize_dynamic_element_spread_attributes(attributes, context, elemen * }); * ``` * Returns true if attribute is deemed reactive, false otherwise. + * @param {import('#compiler').RegularElement} element * @param {import('estree').Identifier} node_id * @param {import('#compiler').Attribute} attribute * @param {import('../types.js').ComponentContext} context * @returns {boolean} */ -function serialize_element_attribute_update_assignment(node_id, attribute, context) { +function serialize_element_attribute_update_assignment(element, node_id, attribute, context) { const state = context.state; - const name = get_attribute_name(attribute, state); + const name = get_attribute_name(element, attribute, context); let [contains_call_expression, value] = serialize_attribute_value(attribute.value, context); // The foreign namespace doesn't have any special handling, everything goes through the attr function @@ -671,21 +705,6 @@ function collect_parent_each_blocks(context) { ); } -/** - * @param {import('#compiler').Attribute} attribute - * @param {import('../types.js').ComponentClientTransformState} state - */ -function get_attribute_name(attribute, state) { - let name = attribute.name; - if (state.metadata.namespace !== 'foreign') { - name = name.toLowerCase(); - if (name !== 'class' && name in AttributeAliases) { - name = AttributeAliases[name]; - } - } - return name; -} - /** * @param {import('#compiler').Component | import('#compiler').SvelteComponent | import('#compiler').SvelteSelf} node * @param {string} component_name @@ -749,7 +768,23 @@ function serialize_inline_component(node, component_name, context) { const [, value] = serialize_attribute_value(attribute.value, context); if (attribute.metadata.dynamic) { - push_prop(b.get(attribute.name, [b.return(value)])); + let arg = value; + + const contains_call_expression = + Array.isArray(attribute.value) && + attribute.value.some((n) => { + return n.type === 'ExpressionTag' && n.metadata.contains_call_expression; + }); + + if (contains_call_expression) { + const id = b.id(context.state.scope.generate(attribute.name)); + context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value)))); + arg = b.call('$.get', id); + } + + if (context.state.options.dev) arg = b.call('$.readonly', arg); + + push_prop(b.get(attribute.name, [b.return(arg)])); } else { push_prop(b.init(attribute.name, value)); } @@ -890,6 +925,10 @@ function serialize_inline_component(node, component_name, context) { if (bind_this !== null) { const prev = fn; const assignment = b.assignment('=', bind_this, b.id('$$value')); + const bind_this_id = /** @type {import('estree').Expression} */ ( + // if expression is not an identifier, we know it can't be a signal + bind_this.type === 'Identifier' ? bind_this : undefined + ); fn = (node_id) => b.call( '$.bind_this', @@ -897,7 +936,8 @@ function serialize_inline_component(node, component_name, context) { b.arrow( [b.id('$$value')], serialize_set_binding(assignment, context, () => context.visit(assignment)) - ) + ), + bind_this_id ); } @@ -973,9 +1013,6 @@ function create_block(parent, name, nodes, context) { /** @type {import('estree').Statement | undefined} */ let close = undefined; - /** @type {import('estree').Identifier | undefined} */ - let id = undefined; - /** @type {import('../types').ComponentClientTransformState} */ const state = { ...context.state, @@ -998,7 +1035,7 @@ function create_block(parent, name, nodes, context) { if (is_single_element) { const element = /** @type {import('#compiler').RegularElement} */ (trimmed[0]); - id = b.id(context.state.scope.generate(element.name)); + const id = b.id(context.state.scope.generate(element.name)); context.visit(element, { ...state, @@ -1013,7 +1050,7 @@ function create_block(parent, name, nodes, context) { body.push( b.var( - id.name, + id, b.call( '$.open', b.id('$$anchor'), @@ -1027,15 +1064,30 @@ function create_block(parent, name, nodes, context) { } else if (is_single_child_not_needing_template) { context.visit(trimmed[0], state); body.push(...state.init); - } else { - id = b.id(context.state.scope.generate('fragment')); + } else if (trimmed.length > 0) { + const id = b.id(context.state.scope.generate('fragment')); + const node_id = b.id(context.state.scope.generate('node')); - process_children(trimmed, b.call('$.child_frag', id), { + process_children(trimmed, node_id, { ...context, state }); - if (state.template.length > 0) { + const template = state.template[0]; + + if (state.template.length === 1 && (template === ' ' || template === '<!>')) { + if (template === ' ') { + body.push(b.var(node_id, b.call('$.space', b.id('$$anchor'))), ...state.init); + close = b.stmt(b.call('$.close', b.id('$$anchor'), node_id)); + } else { + body.push( + b.var(id, b.call('$.comment', b.id('$$anchor'))), + b.var(node_id, b.call('$.child_frag', id)), + ...state.init + ); + close = b.stmt(b.call('$.close_frag', b.id('$$anchor'), id)); + } + } else { const callee = namespace === 'svg' ? '$.svg_template' : '$.template'; state.hoisted.push( @@ -1047,7 +1099,7 @@ function create_block(parent, name, nodes, context) { body.push( b.var( - id.name, + id, b.call( '$.open_frag', b.id('$$anchor'), @@ -1055,12 +1107,14 @@ function create_block(parent, name, nodes, context) { template_name ) ), + b.var(node_id, b.call('$.child_frag', id)), ...state.init ); + close = b.stmt(b.call('$.close_frag', b.id('$$anchor'), id)); - } else { - body.push(...state.init); } + } else { + body.push(...state.init); } if (state.update.length > 0 || state.update_effects.length > 0) { @@ -1086,9 +1140,7 @@ function create_block(parent, name, nodes, context) { /** @type {import('estree').Statement} */ (update).leadingComments = [ { type: 'Block', - value: ` Update `, - // @ts-expect-error - has_trailing_newline: true + value: ` Update ` } ]; } @@ -1106,9 +1158,7 @@ function create_block(parent, name, nodes, context) { body[0].leadingComments = [ { type: 'Block', - value: ` Init `, - // @ts-expect-error - has_trailing_newline: true + value: ` Init ` } ]; } @@ -1349,26 +1399,28 @@ function process_children(nodes, parent, { visit, state }) { /** * @param {Sequence} sequence + * @param {boolean} in_fragment */ - function flush_sequence(sequence) { + function flush_sequence(sequence, in_fragment) { if (sequence.length === 1) { const node = sequence[0]; - if (node.type === 'Text') { + if ((in_fragment && node.type === 'ExpressionTag') || node.type === 'Text') { expression = b.call('$.sibling', expression); + } + + if (node.type === 'Text') { state.template.push(node.raw); return; } state.template.push(' '); - const name = state.scope.generate('text'); - state.init.push(b.var(name, expression)); - + const text_id = get_node_id(expression, state, 'text'); const singular = b.stmt( b.call( '$.text_effect', - b.id(name), + text_id, b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression))) ) ); @@ -1381,7 +1433,7 @@ function process_children(nodes, parent, { visit, state }) { grouped: b.stmt( b.call( '$.text', - b.id(name), + text_id, /** @type {import('estree').Expression} */ (visit(node.expression)) ) ) @@ -1391,7 +1443,7 @@ function process_children(nodes, parent, { visit, state }) { b.stmt( b.assignment( '=', - b.id(`${name}.nodeValue`), + b.member(text_id, b.id('nodeValue')), b.call( '$.stringify', /** @type {import('estree').Expression} */ (visit(node.expression)) @@ -1406,17 +1458,16 @@ function process_children(nodes, parent, { visit, state }) { state.template.push(' '); - const name = state.scope.generate('text'); + const text_id = get_node_id(expression, state, 'text'); const contains_call_expression = sequence.some( (n) => n.type === 'ExpressionTag' && n.metadata.contains_call_expression ); - state.init.push(b.var(name, expression)); const assignment = serialize_template_literal(sequence, visit, state)[1]; - const init = b.stmt(b.assignment('=', b.id(`${name}.nodeValue`), assignment)); + const init = b.stmt(b.assignment('=', b.member(text_id, b.id('nodeValue')), assignment)); const singular = b.stmt( b.call( '$.text_effect', - b.id(name), + text_id, b.thunk(serialize_template_literal(sequence, visit, state)[1]) ) ); @@ -1429,13 +1480,13 @@ function process_children(nodes, parent, { visit, state }) { ) { state.update.push({ singular, - grouped: b.stmt(b.call('$.text', b.id(name), assignment)) + grouped: b.stmt(b.call('$.text', text_id, assignment)) }); } else { state.init.push(init); } - expression = b.call('$.sibling', b.id(name)); + expression = b.call('$.sibling', text_id); } for (let i = 0; i < nodes.length; i += 1) { @@ -1445,7 +1496,7 @@ function process_children(nodes, parent, { visit, state }) { sequence.push(node); } else { if (sequence.length > 0) { - flush_sequence(sequence); + flush_sequence(sequence, true); sequence = []; } @@ -1459,9 +1510,6 @@ function process_children(nodes, parent, { visit, state }) { // get hoisted inside clean_nodes? visit(node, state); } else { - const name = state.scope.generate(node.type === 'RegularElement' ? node.name : 'node'); - const id = b.id(name); - // Optimization path for each blocks. If the parent isn't a fragment and it only has // a single child, then we can classify the block as being "controlled". if ( @@ -1474,7 +1522,12 @@ function process_children(nodes, parent, { visit, state }) { node.metadata.is_controlled = true; visit(node, state); } else { - state.init.push(b.var(name, expression)); + const id = get_node_id( + expression, + state, + node.type === 'RegularElement' ? node.name : 'node' + ); + expression = b.call('$.sibling', id); visit(node, { @@ -1487,8 +1540,24 @@ function process_children(nodes, parent, { visit, state }) { } if (sequence.length > 0) { - flush_sequence(sequence); + flush_sequence(sequence, false); + } +} + +/** + * @param {import('estree').Expression} expression + * @param {import('../types.js').ComponentClientTransformState} state + * @param {string} name + */ +function get_node_id(expression, state, name) { + let id = expression; + + if (id.type !== 'Identifier') { + id = b.id(state.scope.generate(name)); + + state.init.push(b.var(id, expression)); } + return id; } /** @@ -1550,37 +1619,8 @@ function serialize_template_literal(values, visit, state) { if (node.type === 'ExpressionTag' && node.metadata.contains_call_expression) { contains_call_expression = true; } - let expression = visit(node.expression); - if (node.expression.type === 'Identifier') { - const name = node.expression.name; - const binding = scope.get(name); - // When we combine expressions as part of a single template element, we might - // be referencing variables that can be mutated, but are not actually state. - // In order to prevent this undesired behavior, we need ensure we cache the - // latest value we have of that variable before we process the template, enforcing - // the value remains static through the lifetime of the template. - if (binding !== null && binding.kind === 'normal' && binding.mutated) { - let has_already_cached = false; - // Check if we already create a const of this expression - for (let node of state.init) { - if ( - node.type === 'VariableDeclaration' && - node.declarations[0].id.type === 'Identifier' && - node.declarations[0].id.name === name + '_const' - ) { - has_already_cached = true; - expression = b.id(name + '_const'); - break; - } - } - if (!has_already_cached) { - const tmp_id = scope.generate(name + '_const'); - state.init.push(b.const(tmp_id, expression)); - expression = b.id(tmp_id); - } - } - } - expressions.push(b.call('$.stringify', expression)); + + expressions.push(b.call('$.stringify', visit(node.expression))); quasis.push(b.quasi('', i + 1 === values.length)); } } @@ -1614,19 +1654,20 @@ export const template_visitors = { ); }, ConstTag(node, { state, visit }) { + const declaration = node.declaration.declarations[0]; // TODO we can almost certainly share some code with $derived(...) - if (node.expression.left.type === 'Identifier') { + if (declaration.id.type === 'Identifier') { state.init.push( b.const( - node.expression.left, + declaration.id, b.call( '$.derived', - b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression.right))) + b.thunk(/** @type {import('estree').Expression} */ (visit(declaration.init))) ) ) ); } else { - const identifiers = extract_identifiers(node.expression.left); + const identifiers = extract_identifiers(declaration.id); const tmp = b.id(state.scope.generate('computed_const')); // Make all identifiers that are declared within the following computed regular @@ -1642,8 +1683,8 @@ export const template_visitors = { [], b.block([ b.const( - /** @type {import('estree').Pattern} */ (visit(node.expression.left)), - /** @type {import('estree').Expression} */ (visit(node.expression.right)) + /** @type {import('estree').Pattern} */ (visit(declaration.id)), + /** @type {import('estree').Expression} */ (visit(declaration.init)) ), b.return(b.object(identifiers.map((node) => b.prop('init', node, node)))) ]) @@ -1695,18 +1736,20 @@ export const template_visitors = { if (node.argument) { args.push(b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.argument)))); } - const snippet_function = /** @type {import('estree').Expression} */ ( + + let snippet_function = /** @type {import('estree').Expression} */ ( context.visit(node.expression) ); - const init = b.call( - context.state.options.dev ? b.call('$.validate_snippet', snippet_function) : snippet_function, - ...args - ); + if (context.state.options.dev) { + snippet_function = b.call('$.validate_snippet', snippet_function); + } if (is_reactive) { - context.state.init.push(b.stmt(b.call('$.snippet_effect', b.thunk(init)))); + context.state.init.push( + b.stmt(b.call('$.snippet_effect', b.thunk(snippet_function), ...args)) + ); } else { - context.state.init.push(b.stmt(init)); + context.state.init.push(b.stmt(b.call(snippet_function, ...args))); } }, AnimateDirective(node, { state, visit }) { @@ -1871,7 +1914,7 @@ export const template_visitors = { // Then do attributes let is_attributes_reactive = false; if (node.metadata.has_spread) { - const spread_id = serialize_element_spread_attributes(attributes, context, node_id); + const spread_id = serialize_element_spread_attributes(attributes, context, node, node_id); if (child_metadata.namespace !== 'foreign') { add_select_to_spread_update(spread_id, node, context, node_id); } @@ -1892,7 +1935,7 @@ export const template_visitors = { attribute.name !== 'autofocus' && (attribute.value === true || is_text_attribute(attribute)) ) { - const name = get_attribute_name(attribute, context.state); + const name = get_attribute_name(node, attribute, context); const literal_value = /** @type {import('estree').Literal} */ ( serialize_attribute_value(attribute.value, context)[1] ).value; @@ -1913,7 +1956,7 @@ export const template_visitors = { const is = is_custom_element && child_metadata.namespace !== 'foreign' ? serialize_custom_element_attribute_update_assignment(node_id, attribute, context) - : serialize_element_attribute_update_assignment(node_id, attribute, context); + : serialize_element_attribute_update_assignment(node, node_id, attribute, context); if (is) is_attributes_reactive = true; } } @@ -2062,7 +2105,9 @@ export const template_visitors = { '$.element', context.state.node, get_tag, - b.arrow([element_id, b.id('$$anchor')], b.block(inner)), + inner.length === 0 + ? /** @type {any} */ (undefined) + : b.arrow([element_id, b.id('$$anchor')], b.block(inner)), namespace === 'http://www.w3.org/2000/svg' ? b.literal(true) : /** @type {any} */ (undefined) @@ -2089,14 +2134,13 @@ export const template_visitors = { // array or use nested reactivity through runes. // TODO this feels a bit "hidden performance boost"-style, investigate if there's a way // to make this apply in more cases - /** @type {number} */ - let each_type; + let each_type = 0; if ( node.key && (node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index) ) { - each_type = EACH_KEYED; + each_type |= EACH_KEYED; if ( node.key.type === 'Identifier' && node.context.type === 'Identifier' && @@ -2113,12 +2157,17 @@ export const template_visitors = { each_type |= EACH_INDEX_REACTIVE; } } else { - each_type = EACH_ITEM_REACTIVE; + each_type |= EACH_ITEM_REACTIVE; } + if (each_node_meta.is_controlled) { each_type |= EACH_IS_CONTROLLED; } + if (context.state.analysis.immutable) { + each_type |= EACH_IS_IMMUTABLE; + } + // Find the parent each blocks which contain the arrays to invalidate // TODO decide how much of this we want to keep for runes mode. For now we're bailing out below const indirect_dependencies = collect_parent_each_blocks(context).flatMap((block) => { @@ -2142,17 +2191,15 @@ export const template_visitors = { /** * @param {import('estree').Pattern} expression_for_id - * @param {import('estree').Expression} expression_for_other * @returns {import('#compiler').Binding['mutation']} */ - const create_mutation = (expression_for_id, expression_for_other) => { + const create_mutation = (expression_for_id) => { return (assignment, context) => { if (assignment.left.type !== 'Identifier' && assignment.left.type !== 'MemberExpression') { // serialize_set_binding turns other patterns into IIFEs and separates the assignments // into separate expressions, at which point this is called again with an identifier or member expression return serialize_set_binding(assignment, context, () => assignment); } - const left = object(assignment.left); const value = get_assignment_value(assignment, context); const invalidate = b.call( @@ -2165,11 +2212,7 @@ export const template_visitors = { return context.state.analysis.runes ? assign : b.sequence([assign, invalidate]); } else { const original_left = /** @type {import('estree').MemberExpression} */ (assignment.left); - const left = b.member( - expression_for_other, - context.visit(original_left).property, - original_left.computed - ); + const left = context.visit(original_left); const assign = b.assignment(assignment.operator, left, value); return context.state.analysis.runes ? assign : b.sequence([assign, invalidate]); } @@ -2195,8 +2238,7 @@ export const template_visitors = { each_node_meta.array_name ? b.call(each_node_meta.array_name) : collection, index, true - ), - binding.expression + ) ); } else { const unwrapped = binding.expression; @@ -2224,8 +2266,7 @@ export const template_visitors = { binding.expression = b.call(name); binding.mutation = create_mutation( - /** @type {import('estree').Pattern} */ (path.update_expression(unwrapped)), - binding.expression + /** @type {import('estree').Pattern} */ (path.update_expression(unwrapped)) ); } } @@ -2251,12 +2292,6 @@ export const template_visitors = { ) : b.literal(null); - if (context.state.options.dev && key_function.type !== 'Literal') { - context.state.init.push( - b.stmt(b.call('$.validate_each_keys', b.thunk(collection), key_function)) - ); - } - if (node.index && each_node_meta.contains_group_binding) { // We needed to create a unique identifier for the index above, but we want to use the // original index name in the template, therefore create another binding @@ -2264,6 +2299,12 @@ export const template_visitors = { } if ((each_type & EACH_KEYED) !== 0) { + if (context.state.options.dev && key_function.type !== 'Literal') { + context.state.init.push( + b.stmt(b.call('$.validate_each_keys', b.thunk(collection), key_function)) + ); + } + context.state.after_update.push( b.stmt( b.call( @@ -2422,7 +2463,7 @@ export const template_visitors = { body = /** @type {import('estree').BlockStatement} */ (context.visit(node.body)); } - context.state.init.push(b.function_declaration(node.expression, args, body)); + context.state.init.push(b.const(node.expression, b.arrow(args, body))); if (context.state.options.dev) { context.state.init.push(b.stmt(b.call('$.add_snippet_symbol', node.expression))); } @@ -2463,7 +2504,7 @@ export const template_visitors = { next(); }, BindDirective(node, context) { - const { state, path } = context; + const { state, path, visit } = context; /** @type {import('estree').Expression[]} */ const properties = []; @@ -2594,9 +2635,16 @@ export const template_visitors = { } case 'this': - call_expr = b.call(`$.bind_this`, state.node, setter); + call_expr = b.call( + `$.bind_this`, + state.node, + setter, + /** @type {import('estree').Expression} */ ( + // if expression is not an identifier, we know it can't be a signal + node.expression.type === 'Identifier' ? node.expression : undefined + ) + ); break; - case 'textContent': case 'innerHTML': case 'innerText': @@ -2730,19 +2778,9 @@ export const template_visitors = { serialize_event_attribute(node, context); } }, - LetDirective(node, { state, path }) { + LetDirective(node, { state }) { // let:x --> const x = $.derived(() => $.unwrap($$slotProps).x); // let:x={{y, z}} --> const derived_x = $.derived(() => { const { y, z } = $.unwrap($$slotProps).x; return { y, z })); - const parent = path.at(-1); - if ( - parent === undefined || - (parent.type !== 'Component' && - parent.type !== 'RegularElement' && - parent.type !== 'SvelteFragment') - ) { - error(node, 'INTERNAL', 'let directive at invalid position'); - } - if (node.expression && node.expression.type !== 'Identifier') { const name = state.scope.generate(node.name); const bindings = state.scope.get_bindings(node); @@ -2921,5 +2959,6 @@ export const template_visitors = { ...context.state, node: b.id('$.document') }); - } + }, + CallExpression: javascript_visitors_runes.CallExpression }; diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index 66459d60d..af8de96d7 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -32,15 +32,11 @@ export function transform_component(analysis, source, options) { program.body[0].leadingComments = [ { type: 'Line', - value: ` ${basename} (Svelte v${VERSION})`, - // @ts-ignore - has_trailing_newline: true + value: ` ${basename} (Svelte v${VERSION})` }, { type: 'Line', - value: ' Note: compiler output will change before 5.0 is released!', - // @ts-ignore - has_trailing_newline: true + value: ' Note: compiler output will change before 5.0 is released!' } ]; } @@ -86,9 +82,7 @@ export function transform_module(analysis, source, options) { program.body[0].leadingComments = [ { type: 'Block', - value: ` ${basename} generated by Svelte v${VERSION} `, - // @ts-ignore - has_trailing_newline: true + value: ` ${basename} generated by Svelte v${VERSION} ` } ]; } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index e008f7c4c..798e58ed1 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -1,11 +1,15 @@ import { walk } from 'zimmerframe'; import { set_scope, get_rune } from '../../scope.js'; -import { extract_identifiers, extract_paths, is_event_attribute } from '../../../utils/ast.js'; +import { + extract_identifiers, + extract_paths, + is_event_attribute, + unwrap_ts_expression +} from '../../../utils/ast.js'; import * as b from '../../../utils/builders.js'; import is_reference from 'is-reference'; import { ContentEditableBindings, - DOMBooleanAttributes, VoidElements, WhitespaceInsensitiveAttributes } from '../../constants.js'; @@ -15,11 +19,12 @@ import { escape_html, infer_namespace } from '../utils.js'; -import { create_attribute, is_element_node } from '../../nodes.js'; +import { create_attribute, is_custom_element_node, is_element_node } from '../../nodes.js'; import { error } from '../../../errors.js'; import { binding_properties } from '../../bindings.js'; import { regex_starts_with_newline, regex_whitespaces_strict } from '../../patterns.js'; import { remove_types } from '../typescript.js'; +import { DOMBooleanAttributes } from '../../../../constants.js'; /** * @param {string} value @@ -168,7 +173,7 @@ function process_children(nodes, parent, { visit, state }) { } const expression = b.call( - '$.escape', + '$.escape_text', /** @type {import('estree').Expression} */ (visit(node.expression)) ); state.template.push(t_expression(expression)); @@ -471,6 +476,25 @@ function serialize_set_binding(node, context, fallback) { return fallback(); } +/** + * @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} element + * @param {import('#compiler').Attribute} attribute + * @param {{ state: { metadata: { namespace: import('#compiler').Namespace }}}} context + */ +function get_attribute_name(element, attribute, context) { + let name = attribute.name; + if ( + element.type === 'RegularElement' && + !element.metadata.svg && + context.state.metadata.namespace !== 'foreign' + ) { + name = name.toLowerCase(); + // don't lookup boolean aliases here, the server runtime function does only + // check for the lowercase variants of boolean attributes + } + return name; +} + /** @type {import('./types').Visitors} */ const global_visitors = { Identifier(node, { path, state }) { @@ -549,8 +573,9 @@ const javascript_visitors_runes = { const declarations = []; for (const declarator of node.declarations) { - const rune = get_rune(declarator.init, state.scope); - if (!rune) { + const init = unwrap_ts_expression(declarator.init); + const rune = get_rune(init, state.scope); + if (!rune || rune === '$effect.active' || rune === '$inspect') { declarations.push(/** @type {import('estree').VariableDeclarator} */ (visit(declarator))); continue; } @@ -560,7 +585,7 @@ const javascript_visitors_runes = { continue; } - const args = /** @type {import('estree').CallExpression} */ (declarator.init).arguments; + const args = /** @type {import('estree').CallExpression} */ (init).arguments; const value = args.length === 0 ? b.id('undefined') @@ -604,6 +629,27 @@ const javascript_visitors_runes = { } } context.next(); + }, + CallExpression(node, { state, next, visit }) { + const rune = get_rune(node, state.scope); + + if (rune === '$effect.active') { + return b.literal(false); + } + + if (rune === '$inspect') { + if (state.options.dev) { + const args = /** @type {import('estree').Expression[]} */ ( + node.arguments.map((arg) => visit(arg)) + ); + + return b.call('console.log', ...args); + } + + return b.unary('void', b.literal(0)); + } + + next(); } }; @@ -681,12 +727,14 @@ function serialize_attribute_value( /** * + * @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} element * @param {Array<import('#compiler').Attribute | import('#compiler').SpreadAttribute>} attributes * @param {import('#compiler').StyleDirective[]} style_directives * @param {import('#compiler').ClassDirective[]} class_directives * @param {import('./types').ComponentContext} context */ function serialize_element_spread_attributes( + element, attributes, style_directives, class_directives, @@ -697,7 +745,7 @@ function serialize_element_spread_attributes( for (const attribute of attributes) { if (attribute.type === 'Attribute') { - const name = attribute.name.toLowerCase(); + const name = get_attribute_name(element, attribute, context); const value = serialize_attribute_value( attribute.value, context, @@ -709,8 +757,18 @@ function serialize_element_spread_attributes( } } + const lowercase_attributes = + element.type !== 'RegularElement' || element.metadata.svg || is_custom_element_node(element) + ? b.false + : b.true; + const is_svg = element.type === 'RegularElement' && element.metadata.svg ? b.true : b.false; /** @type {import('estree').Expression[]} */ - const args = [b.array(values), b.literal(context.state.analysis.stylesheet.id)]; + const args = [ + b.array(values), + lowercase_attributes, + is_svg, + b.literal(context.state.analysis.stylesheet.id) + ]; if (style_directives.length > 0 || class_directives.length > 0) { const styles = style_directives.map((directive) => @@ -1040,8 +1098,9 @@ const template_visitors = { state.template.push(t_expression(id)); }, ConstTag(node, { state, visit }) { - const pattern = /** @type {import('estree').Pattern} */ (visit(node.expression.left)); - const init = /** @type {import('estree').Expression} */ (visit(node.expression.right)); + const declaration = node.declaration.declarations[0]; + const pattern = /** @type {import('estree').Pattern} */ (visit(declaration.id)); + const init = /** @type {import('estree').Expression} */ (visit(declaration.init)); state.init.push(b.declaration('const', pattern, init)); }, DebugTag(node, { state, visit }) { @@ -1066,11 +1125,17 @@ const template_visitors = { ); }, RenderTag(node, context) { - const snippet_function = context.state.options.dev + const state = context.state; + const [anchor, anchor_id] = serialize_anchor(state); + + state.init.push(anchor); + state.template.push(t_expression(anchor_id)); + + const snippet_function = state.options.dev ? b.call('$.validate_snippet', node.expression) : node.expression; if (node.argument) { - context.state.template.push( + state.template.push( t_statement( b.stmt( b.call( @@ -1082,8 +1147,10 @@ const template_visitors = { ) ); } else { - context.state.template.push(t_statement(b.stmt(b.call(snippet_function, b.id('$$payload'))))); + state.template.push(t_statement(b.stmt(b.call(snippet_function, b.id('$$payload'))))); } + + state.template.push(t_expression(anchor_id)); }, ClassDirective(node) { error(node, 'INTERNAL', 'Node should have been handled elsewhere'); @@ -1376,8 +1443,6 @@ const template_visitors = { state.template.push(t_expression(id)); }, SnippetBlock(node, context) { - const [dec, id] = serialize_anchor(context.state); - // TODO hoist where possible /** @type {import('estree').Pattern[]} */ const args = [b.id('$$payload')]; @@ -1385,18 +1450,11 @@ const template_visitors = { args.push(node.context); } - const out = b.member_id('$$payload.out'); - context.state.init.push( b.function_declaration( node.expression, args, - b.block([ - dec, - b.stmt(b.assignment('+=', out, id)), - .../** @type {import('estree').BlockStatement} */ (context.visit(node.body)).body, - b.stmt(b.assignment('+=', out, id)) - ]) + /** @type {import('estree').BlockStatement} */ (context.visit(node.body)) ) ); if (context.state.options.dev) { @@ -1437,17 +1495,7 @@ const template_visitors = { state.template.push(t_statement(call)); state.template.push(t_expression(id)); }, - LetDirective(node, { state, path }) { - const parent = path.at(-1); - if ( - parent === undefined || - (parent.type !== 'Component' && - parent.type !== 'RegularElement' && - parent.type !== 'SvelteFragment') - ) { - error(node, 'INTERNAL', 'let directive at invalid position'); - } - + LetDirective(node, { state }) { if (node.expression && node.expression.type !== 'Identifier') { const name = state.scope.generate(node.name); const bindings = state.scope.get_bindings(node); @@ -1579,7 +1627,9 @@ const template_visitors = { b.stmt(b.call('$.head', b.id('$$payload'), b.arrow([b.id('$$payload')], b.block(body)))) ) ); - } + }, + // @ts-ignore: need to extract this out somehow + CallExpression: javascript_visitors_runes.CallExpression }; /** @@ -1749,11 +1799,17 @@ function serialize_element_attributes(node, context) { context.state.init.push(...lets); if (has_spread) { - serialize_element_spread_attributes(attributes, style_directives, class_directives, context); + serialize_element_spread_attributes( + node, + attributes, + style_directives, + class_directives, + context + ); } else { for (const attribute of /** @type {import('#compiler').Attribute[]} */ (attributes)) { if (attribute.value === true || is_text_attribute(attribute)) { - const name = attribute.name.toLowerCase(); + const name = get_attribute_name(node, attribute, context); const literal_value = /** @type {import('estree').Literal} */ ( serialize_attribute_value( attribute.value, @@ -1775,7 +1831,7 @@ function serialize_element_attributes(node, context) { continue; } - const name = attribute.name.toLowerCase(); + const name = get_attribute_name(node, attribute, context); const is_boolean = DOMBooleanAttributes.includes(name); const value = serialize_attribute_value( attribute.value, @@ -2038,7 +2094,7 @@ export function server_component(analysis, options) { } const component_block = b.block([ - b.stmt(b.call('$.push', b.literal(analysis.runes), ...(options.immutable ? [b.true] : []))), + b.stmt(b.call('$.push', b.literal(analysis.runes))), .../** @type {import('estree').Statement[]} */ (instance.body), .../** @type {import('estree').Statement[]} */ (template.body), b.stmt(b.call('$.pop')) diff --git a/packages/svelte/src/compiler/phases/constants.js b/packages/svelte/src/compiler/phases/constants.js index b0fbe2ecd..d31a30f05 100644 --- a/packages/svelte/src/compiler/phases/constants.js +++ b/packages/svelte/src/compiler/phases/constants.js @@ -1,52 +1,12 @@ -export const DOMBooleanAttributes = [ - 'allowfullscreen', - 'async', - 'autofocus', - 'autoplay', - 'checked', - 'controls', - 'default', - 'disabled', - 'formnovalidate', - 'hidden', - 'indeterminate', - 'ismap', - 'loop', - 'multiple', - 'muted', - 'nomodule', - 'novalidate', - 'open', - 'playsinline', - 'readonly', - 'required', - 'reversed', - 'seamless', - 'selected' -]; +import { AttributeAliases, DOMBooleanAttributes } from '../../constants.js'; export const DOMProperties = [ - 'className', + ...Object.values(AttributeAliases), 'value', - 'readOnly', - 'formNoValidate', - 'isMap', - 'noModule', - 'playsInline', 'inert', ...DOMBooleanAttributes ]; -/** @type {Record<string, string>} */ -export const AttributeAliases = { - class: 'className', - formnovalidate: 'formNoValidate', - ismap: 'isMap', - nomodule: 'noModule', - playsinline: 'playsInline', - readonly: 'readOnly' -}; - export const VoidElements = [ 'area', 'base', @@ -110,7 +70,16 @@ export const ElementBindings = [ 'indeterminate' ]; -export const Runes = ['$state', '$props', '$derived', '$effect']; +export const Runes = /** @type {const} */ ([ + '$state', + '$props', + '$derived', + '$effect', + '$effect.pre', + '$effect.active', + '$effect.root', + '$inspect' +]); /** * Whitespace inside one of these elements will not result in @@ -215,3 +184,15 @@ export const SVGElements = [ 'view', 'vkern' ]; + +export const EventModifiers = [ + 'preventDefault', + 'stopPropagation', + 'stopImmediatePropagation', + 'capture', + 'once', + 'passive', + 'nonpassive', + 'self', + 'trusted' +]; diff --git a/packages/svelte/src/compiler/phases/patterns.js b/packages/svelte/src/compiler/phases/patterns.js index c6abc94d5..880296dd5 100644 --- a/packages/svelte/src/compiler/phases/patterns.js +++ b/packages/svelte/src/compiler/phases/patterns.js @@ -20,3 +20,4 @@ export const regex_special_chars = /[\d+`!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~]/; export const regex_starts_with_vowel = /^[aeiou]/; export const regex_heading_tags = /^h[1-6]$/; +export const regex_illegal_attribute_character = /(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/; diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 22e5d3eae..09b3e8934 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -3,11 +3,7 @@ import { walk } from 'zimmerframe'; import { is_element_node } from './nodes.js'; import * as b from '../utils/builders.js'; import { error } from '../errors.js'; -import { - extract_identifiers, - extract_identifiers_from_expression, - get_callee_name -} from '../utils/ast.js'; +import { extract_identifiers, extract_identifiers_from_expression } from '../utils/ast.js'; import { Runes } from './constants.js'; export class Scope { @@ -91,6 +87,11 @@ export class Scope { } } + if (this.declarations.has(node.name)) { + // This also errors on var/function types, but that's arguably a good thing + error(node, 'duplicate-declaration', node.name); + } + /** @type {import('#compiler').Binding} */ const binding = { node, @@ -104,7 +105,8 @@ export class Scope { is_called: false, prop_alias: null, expression: null, - mutation: null + mutation: null, + reassigned: false }; this.declarations.set(node.name, binding); this.root.conflicts.add(node.name); @@ -174,18 +176,19 @@ export class Scope { * @param {import('#compiler').SvelteNode[]} path */ reference(node, path) { + path = [...path]; // ensure that mutations to path afterwards don't affect this reference let references = this.references.get(node.name); if (!references) this.references.set(node.name, (references = [])); references.push({ node, path }); - const declaration = this.declarations.get(node.name); - if (declaration) { - declaration.references.push({ node, path }); + const binding = this.declarations.get(node.name); + if (binding) { + binding.references.push({ node, path }); } else if (this.#parent) { this.#parent.reference(node, path); } else { - // no declaration was found, and this is the top level scope, + // no binding was found, and this is the top level scope, // which means this is a global this.root.conflicts.add(node.name); } @@ -253,7 +256,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { function add_params(scope, params) { for (const param of params) { for (const node of extract_identifiers(param)) { - scope.declare(node, 'normal', 'param'); + scope.declare(node, 'normal', param.type === 'RestElement' ? 'rest_param' : 'param'); } } } @@ -373,7 +376,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { scopes.set(child, state.scope); visit(child); } else if (child.type === 'SnippetBlock') { - visit(child); + visit(child, { scope }); } else { visit(child, { scope }); } @@ -437,11 +440,12 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { SwitchStatement: create_block_scope, ClassDeclaration(node, { state, next }) { - if (node.id) state.scope.declare(node.id, 'normal', 'const'); + if (node.id) state.scope.declare(node.id, 'normal', 'const', node); next(); }, - VariableDeclaration(node, { state, next }) { + VariableDeclaration(node, { state, path, next }) { + const is_parent_const_tag = path.at(-1)?.type === 'ConstTag'; for (const declarator of node.declarations) { /** @type {import('#compiler').Binding[]} */ const bindings = []; @@ -449,7 +453,12 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { state.scope.declarators.set(declarator, bindings); for (const id of extract_identifiers(declarator.id)) { - const binding = state.scope.declare(id, 'normal', node.kind, declarator.init); + const binding = state.scope.declare( + id, + is_parent_const_tag ? 'derived' : 'normal', + node.kind, + declarator.init + ); bindings.push(binding); } } @@ -499,12 +508,10 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } if (node.index) { - scope.declare( - b.id(node.index), - // TODO see logic in EachBlock in dom.ts - node.key ? 'derived' : 'normal', - 'const' - ); + const is_keyed = + node.key && + (node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index); + scope.declare(b.id(node.index), is_keyed ? 'derived' : 'normal', 'const'); } if (node.key) visit(node.key, { scope }); @@ -561,6 +568,7 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { }, SnippetBlock(node, context) { + const state = context.state; // Special-case for root-level snippets: they become part of the instance scope const is_top_level = !context.path.at(-2); let scope = state.scope; @@ -595,17 +603,6 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { ) ]); context.next(); - }, - - ConstTag(node, { state, next }) { - for (const identifier of extract_identifiers(node.expression.left)) { - state.scope.declare( - /** @type {import('estree').Identifier} */ (identifier), - 'derived', - 'const' - ); - } - next(); } // TODO others @@ -636,7 +633,10 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } else { extract_identifiers(node).forEach((identifier) => { const binding = scope.get(identifier.name); - if (binding) binding.mutated = true; + if (binding) { + binding.mutated = true; + binding.reassigned = true; + } }); } } @@ -668,15 +668,32 @@ export function set_scope(scopes) { /** * Returns the name of the rune if the given expression is a `CallExpression` using a rune. - * @param {import('estree').Expression | null | undefined} node + * @param {import('estree').Node | null | undefined} node * @param {Scope} scope + * @returns {Runes[number] | null} */ export function get_rune(node, scope) { - const callee = get_callee_name(node); - if (callee === null || !Runes.includes(callee)) return null; + if (!node) return null; + if (node.type !== 'CallExpression') return null; + + let n = node.callee; + + let joined = ''; + + while (n.type === 'MemberExpression') { + if (n.computed) return null; + if (n.property.type !== 'Identifier') return null; + joined = '.' + n.property.name + joined; + n = n.object; + } + + if (n.type !== 'Identifier') return null; + + joined = n.name + joined; + if (!Runes.includes(/** @type {any} */ (joined))) return null; - const binding = scope.get(callee); + const binding = scope.get(n.name); if (binding !== null) return null; // rune name, but references a variable or store - return callee; + return /** @type {Runes[number] | null} */ (joined); } diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 8ee2cce17..f2f9868e1 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -46,6 +46,8 @@ export interface Analysis { module: Js; name: string; // TODO should this be filename? it's used in `compileModule` as well as `compile` warnings: RawWarning[]; + runes: boolean; + immutable: boolean; // TODO figure out if we can move this to ComponentAnalysis accessors: boolean; diff --git a/packages/svelte/src/compiler/types/css.d.ts b/packages/svelte/src/compiler/types/css.d.ts index e4d587ef6..49a3fc514 100644 --- a/packages/svelte/src/compiler/types/css.d.ts +++ b/packages/svelte/src/compiler/types/css.d.ts @@ -59,7 +59,7 @@ export interface PseudoElementSelector extends BaseNode { export interface PseudoClassSelector extends BaseNode { type: 'PseudoClassSelector'; name: string; - args: string | null; + args: SelectorList | null; } export interface Percentage extends BaseNode { @@ -67,6 +67,11 @@ export interface Percentage extends BaseNode { value: string; } +export interface Nth extends BaseNode { + type: 'Nth'; + value: string; +} + export type SimpleSelector = | TypeSelector | IdSelector @@ -74,7 +79,8 @@ export type SimpleSelector = | AttributeSelector | PseudoElementSelector | PseudoClassSelector - | Percentage; + | Percentage + | Nth; export interface Combinator extends BaseNode { type: 'Combinator'; diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index 2c5480eb7..6435c2020 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -237,6 +237,7 @@ export type DeclarationKind = | 'function' | 'import' | 'param' + | 'rest_param' | 'synthetic'; export interface Binding { @@ -250,6 +251,7 @@ export interface Binding { * - `each`: An each block context variable * - `store_sub`: A $store value * - `legacy_reactive`: A `$:` declaration + * - `legacy_reactive_import`: An imported binding that is mutated inside the component */ kind: | 'normal' @@ -259,7 +261,8 @@ export interface Binding { | 'derived' | 'each' | 'store_sub' - | 'legacy_reactive'; + | 'legacy_reactive' + | 'legacy_reactive_import'; declaration_kind: DeclarationKind; /** * What the value was initialized with. @@ -269,6 +272,7 @@ export interface Binding { is_called: boolean; references: { node: Identifier; path: SvelteNode[] }[]; mutated: boolean; + reassigned: boolean; scope: Scope; /** For `legacy_reactive`: its reactive dependencies */ legacy_dependencies: Binding[]; diff --git a/packages/svelte/src/compiler/types/legacy-nodes.d.ts b/packages/svelte/src/compiler/types/legacy-nodes.d.ts index 6564dc47a..41c1ad7a3 100644 --- a/packages/svelte/src/compiler/types/legacy-nodes.d.ts +++ b/packages/svelte/src/compiler/types/legacy-nodes.d.ts @@ -1,6 +1,7 @@ import type { StyleDirective as LegacyStyleDirective, Text } from '#compiler'; import type { ArrayExpression, + AssignmentExpression, Expression, Identifier, MemberExpression, @@ -168,6 +169,11 @@ export interface LegacyTitle extends BaseElement { name: 'title'; } +export interface LegacyConstTag extends BaseNode { + type: 'ConstTag'; + expression: AssignmentExpression; +} + export interface LegacyTransition extends BaseNode { type: 'Transition'; /** The 'x' in `transition:x` */ @@ -215,6 +221,7 @@ export type LegacyElementLike = | LegacyWindow; export type LegacySvelteNode = + | LegacyConstTag | LegacyElementLike | LegacyAttributeLike | LegacyAttributeShorthand diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index b69173e6b..6343b7ae3 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -2,7 +2,8 @@ import type { Binding } from '#compiler'; import type { ArrayExpression, ArrowFunctionExpression, - AssignmentExpression, + VariableDeclaration, + VariableDeclarator, Expression, FunctionDeclaration, FunctionExpression, @@ -130,7 +131,9 @@ export interface Comment extends BaseNode { /** A `{@const ...}` tag */ export interface ConstTag extends BaseNode { type: 'ConstTag'; - expression: AssignmentExpression; + declaration: VariableDeclaration & { + declarations: [VariableDeclarator & { id: Pattern; init: Expression }]; + }; } /** A `{@debug ...}` tag */ diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js index 416c22358..dd9bbfc17 100644 --- a/packages/svelte/src/compiler/utils/ast.js +++ b/packages/svelte/src/compiler/utils/ast.js @@ -20,19 +20,6 @@ export function object(expression) { return expression; } -/** - * Returns the name of callee if the given expression is a call expression. - * @param {import('estree').Expression | null | undefined} node - */ -export function get_callee_name(node) { - if (!node) return null; - if (node.type !== 'CallExpression') return null; - if (node.callee.type !== 'Identifier' && node.callee.type !== 'MemberExpression') return null; - - const id = object(node.callee); - return id === null ? null : id.name; -} - /** * Returns true if the attribute contains a single static text node. * @param {import('#compiler').Attribute} attribute @@ -278,3 +265,75 @@ function _extract_paths(assignments = [], param, expression, update_expression) return assignments; } + +/** + * The Acorn TS plugin defines `foo!` as a `TSNonNullExpression` node, and + * `foo as Bar` as a `TSAsExpression` node. This function unwraps those. + * + * @template {import('#compiler').SvelteNode | undefined | null} T + * @param {T} node + * @returns {T} + */ +export function unwrap_ts_expression(node) { + if (!node) { + return node; + } + + // @ts-expect-error these types don't exist on the base estree types + if (node.type === 'TSNonNullExpression' || node.type === 'TSAsExpression') { + // @ts-expect-error + return node.expression; + } + + return node; +} + +/** + * Like `path.at(x)`, but skips over `TSNonNullExpression` and `TSAsExpression` nodes and eases assertions a bit + * by removing the `| undefined` from the resulting type. + * + * @template {import('#compiler').SvelteNode} T + * @param {T[]} path + * @param {number} at + */ +export function get_parent(path, at) { + let node = path.at(at); + // @ts-expect-error + if (node.type === 'TSNonNullExpression' || node.type === 'TSAsExpression') { + return /** @type {T} */ (path.at(at < 0 ? at - 1 : at + 1)); + } + return /** @type {T} */ (node); +} + +/** + * Returns `true` if the expression is an identifier, a literal, a function expression, + * or a logical expression that only contains simple expressions. Used to determine whether + * something needs to be treated as though accessing it could have side-effects (i.e. + * reading signals prematurely) + * @param {import('estree').Expression} node + * @returns {boolean} + */ +export function is_simple_expression(node) { + if ( + node.type === 'Literal' || + node.type === 'Identifier' || + node.type === 'ArrowFunctionExpression' || + node.type === 'FunctionExpression' + ) { + return true; + } + + if (node.type === 'ConditionalExpression') { + return ( + is_simple_expression(node.test) && + is_simple_expression(node.consequent) && + is_simple_expression(node.alternate) + ); + } + + if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') { + return is_simple_expression(node.left) && is_simple_expression(node.right); + } + + return false; +} diff --git a/packages/svelte/src/compiler/utils/builders.js b/packages/svelte/src/compiler/utils/builders.js index 897153218..dd6cff6f9 100644 --- a/packages/svelte/src/compiler/utils/builders.js +++ b/packages/svelte/src/compiler/utils/builders.js @@ -72,7 +72,7 @@ export function labeled(name, body) { /** * @param {string | import('estree').Expression} callee - * @param {...import('estree').Expression} args + * @param {...(import('estree').Expression | import('estree').SpreadElement)} args * @returns {import('estree').CallExpression} */ export function call(callee, ...args) { diff --git a/packages/svelte/src/compiler/validate-options.js b/packages/svelte/src/compiler/validate-options.js index e928e9bac..575243517 100644 --- a/packages/svelte/src/compiler/validate-options.js +++ b/packages/svelte/src/compiler/validate-options.js @@ -72,7 +72,10 @@ export const validate_component_options = discloseVersion: boolean(true), - immutable: boolean(false), + immutable: deprecate( + 'The immutable option has been deprecated. It has no effect in runes mode.', + boolean(false) + ), legacy: object({ componentApi: boolean(false) @@ -166,6 +169,18 @@ function warn_removed(message) { }; } +/** + * @param {string} message + * @param {Validator} validator + * @returns {Validator} + */ +function deprecate(message, validator) { + return (input, keypath) => { + if (input !== undefined) warn(message); + return validator(input, keypath); + }; +} + /** * @param {Record<string, Validator>} children * @param {boolean} [allow_unknown] diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index 0d0f0dd48..67c2dc333 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -22,8 +22,8 @@ const runes = { `It looks like you're using the $${name} rune, but there is a local binding called ${name}. ` + `Referencing a local variable with a $ prefix will create a store subscription. Please rename ${name} to avoid the ambiguity.`, /** @param {string} name */ - 'state-rune-not-mutated': (name) => - `${name} is declared with $state(...) but is never updated. Did you mean to create a function that changes its value?` + 'non-state-reference': (name) => + `${name} is updated, but is not declared with $state(...). Changing its value will not correctly trigger updates.` }; /** @satisfies {Warnings} */ @@ -115,7 +115,7 @@ const a11y = { 'a11y-misplaced-scope': () => 'A11y: The scope attribute should only be used with <th> elements', 'a11y-positive-tabindex': () => 'A11y: avoid tabindex values above zero', 'a11y-click-events-have-key-events': () => - 'A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type="button"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.', + 'A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type="button"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.', 'a11y-no-noninteractive-tabindex': () => 'A11y: noninteractive element cannot have nonnegative tabIndex value', /** @@ -180,7 +180,7 @@ const a11y = { * @param {string} accompanied_by */ 'a11y-mouse-events-have-key-events': (event, accompanied_by) => - `A11y: on:${event} must be accompanied by on:${accompanied_by}`, + `A11y: '${event}' event must be accompanied by '${accompanied_by}' event`, /** @param {string} name */ 'a11y-missing-content': (name) => `A11y: <${name}> element should have child content` }; @@ -191,13 +191,35 @@ const state = { `State referenced in its own scope will never update. Did you mean to reference it inside a closure?` }; +/** @satisfies {Warnings} */ +const performance = { + 'avoid-inline-class': () => + `Avoid 'new class' — instead, declare the class at the top level scope`, + 'avoid-nested-class': () => `Avoid declaring classes below the top level scope` +}; + +/** @satisfies {Warnings} */ +const components = { + /** @param {string} name */ + 'component-name-lowercase': (name) => + `<${name}> will be treated as an HTML element unless it begins with a capital letter` +}; + +const legacy = { + 'no-reactive-declaration': () => + `Reactive declarations only exist at the top level of the instance script` +}; + /** @satisfies {Warnings} */ const warnings = { ...css, ...attributes, ...runes, ...a11y, - ...state + ...performance, + ...state, + ...components, + ...legacy }; /** @typedef {typeof warnings} AllWarnings */ diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index 17b91d1f1..6b7cc123f 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -3,6 +3,7 @@ export const EACH_INDEX_REACTIVE = 1 << 1; export const EACH_KEYED = 1 << 2; export const EACH_IS_CONTROLLED = 1 << 3; export const EACH_IS_ANIMATED = 1 << 4; +export const EACH_IS_IMMUTABLE = 1 << 6; /** List of Element events that will be delegated */ export const DelegatedEvents = [ @@ -32,3 +33,48 @@ export const DelegatedEvents = [ /** List of Element events that will be delegated and are passive */ export const PassiveDelegatedEvents = ['touchstart', 'touchmove', 'touchend']; + +/** + * @type {Record<string, string>} + * List of attribute names that should be aliased to their property names + * because they behave differently between setting them as an attribute and + * setting them as a property. + */ +export const AttributeAliases = { + // no `class: 'className'` because we handle that separately + formnovalidate: 'formNoValidate', + ismap: 'isMap', + nomodule: 'noModule', + playsinline: 'playsInline', + readonly: 'readOnly' +}; + +/** + * Attributes that are boolean, i.e. they are present or not present. + */ +export const DOMBooleanAttributes = [ + 'allowfullscreen', + 'async', + 'autofocus', + 'autoplay', + 'checked', + 'controls', + 'default', + 'disabled', + 'formnovalidate', + 'hidden', + 'indeterminate', + 'ismap', + 'loop', + 'multiple', + 'muted', + 'nomodule', + 'novalidate', + 'open', + 'playsinline', + 'readonly', + 'required', + 'reversed', + 'seamless', + 'selected' +]; diff --git a/packages/svelte/src/internal/client/custom-element.js b/packages/svelte/src/internal/client/custom-element.js index ded94b616..bafdbc00f 100644 --- a/packages/svelte/src/internal/client/custom-element.js +++ b/packages/svelte/src/internal/client/custom-element.js @@ -127,6 +127,16 @@ if (typeof HTMLElement === 'function') { this.$$d[name] = get_custom_element_value(name, attribute.value, this.$$p_d, 'toProp'); } } + // Port over props that were set programmatically before ce was initialized + for (const key in this.$$p_d) { + // @ts-expect-error + if (!(key in this.$$d) && this[key] !== undefined) { + // @ts-expect-error + this.$$d[key] = this[key]; // don't transform, these were set through JavaScript + // @ts-expect-error + delete this[key]; // remove the property that shadows the getter/setter + } + } this.$$c = createClassComponent({ component: this.$$ctor, target: this.shadowRoot || this, diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js new file mode 100644 index 000000000..f3306ab3a --- /dev/null +++ b/packages/svelte/src/internal/client/each.js @@ -0,0 +1,803 @@ +import { + EACH_INDEX_REACTIVE, + EACH_IS_ANIMATED, + EACH_IS_CONTROLLED, + EACH_IS_IMMUTABLE, + EACH_ITEM_REACTIVE, + EACH_KEYED +} from '../../constants.js'; +import { create_each_block, create_each_item_block } from './block.js'; +import { + current_hydration_fragment, + get_hydration_fragment, + hydrate_block_anchor, + set_current_hydration_fragment +} from './hydration.js'; +import { clear_text_content, map_get, map_set } from './operations.js'; +import { STATE_SYMBOL } from './proxy/proxy.js'; +import { insert, remove } from './reconciler.js'; +import { empty } from './render.js'; +import { + destroy_signal, + execute_effect, + is_lazy_property, + lazy_property, + mutable_source, + push_destroy_fn, + render_effect, + schedule_task, + set_signal_value, + source +} from './runtime.js'; +import { trigger_transitions } from './transitions.js'; +import { is_array } from './utils.js'; + +const NEW_BLOCK = -1; +const MOVED_BLOCK = 99999999; +const LIS_BLOCK = -2; + +function no_op() {} + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {null | ((item: V) => string)} key_fn + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn + * @returns {void} + */ +function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_fn) { + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + const block = create_each_block(flags, anchor_node); + + /** @type {null | import('./types.js').Render} */ + let current_fallback = null; + hydrate_block_anchor(anchor_node, is_controlled); + + /** @type {V[]} */ + let array; + + /** @type {Array<string> | null} */ + let keys = null; + + /** @type {null | import('./types.js').EffectSignal} */ + let render = null; + block.r = + /** @param {import('./types.js').Transition} transition */ + (transition) => { + const fallback = /** @type {import('./types.js').Render} */ (current_fallback); + const transitions = fallback.s; + transitions.add(transition); + transition.f(() => { + transitions.delete(transition); + if (transitions.size === 0) { + if (fallback.e !== null) { + if (fallback.d !== null) { + remove(fallback.d); + fallback.d = null; + } + destroy_signal(fallback.e); + fallback.e = null; + } + } + }); + }; + + const create_fallback_effect = () => { + /** @type {import('./types.js').Render} */ + const fallback = { + d: null, + e: null, + s: new Set(), + p: current_fallback + }; + // Managed effect + const effect = render_effect( + () => { + const dom = block.d; + if (dom !== null) { + remove(dom); + block.d = null; + } + let anchor = block.a; + const is_controlled = (block.f & EACH_IS_CONTROLLED) !== 0; + if (is_controlled) { + anchor = empty(); + block.a.appendChild(anchor); + } + /** @type {(anchor: Node) => void} */ (fallback_fn)(anchor); + fallback.d = block.d; + block.d = null; + }, + block, + true + ); + fallback.e = effect; + current_fallback = fallback; + }; + + const each = render_effect( + () => { + /** @type {V[]} */ + const maybe_array = collection(); + array = is_array(maybe_array) + ? maybe_array + : maybe_array == null + ? [] + : Array.from(maybe_array); + if (key_fn !== null) { + keys = array.map(key_fn); + } else if ((flags & EACH_KEYED) === 0) { + array.map(no_op); + } + const length = array.length; + if (fallback_fn !== null) { + if (length === 0) { + if (block.v.length !== 0 || render === null) { + create_fallback_effect(); + } + } else if (block.v.length === 0 && current_fallback !== null) { + const fallback = current_fallback; + const transitions = fallback.s; + if (transitions.size === 0) { + if (fallback.d !== null) { + remove(fallback.d); + fallback.d = null; + } + } else { + trigger_transitions(transitions, 'out'); + } + } + } + if (render !== null) { + execute_effect(render); + } + }, + block, + false + ); + + render = render_effect( + /** @param {import('./types.js').EachBlock} block */ + (block) => { + const flags = block.f; + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + const anchor_node = block.a; + reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys); + }, + block, + true + ); + + push_destroy_fn(each, () => { + const flags = block.f; + const anchor_node = block.a; + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + let fallback = current_fallback; + while (fallback !== null) { + const dom = fallback.d; + if (dom !== null) { + remove(dom); + } + const effect = fallback.e; + if (effect !== null) { + destroy_signal(effect); + } + fallback = fallback.p; + } + // Clear the array + reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys); + destroy_signal(/** @type {import('./types.js').EffectSignal} */ (render)); + }); + + block.e = each; +} + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {null | ((item: V) => string)} key_fn + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @returns {void} + */ +export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) { + each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_tracked_array); +} + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @returns {void} + */ +export function each_indexed(anchor_node, collection, flags, render_fn, fallback_fn) { + each(anchor_node, collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array); +} + +/** + * @template V + * @param {Array<V>} array + * @param {import('./types.js').EachBlock} each_block + * @param {Element | Comment | Text} dom + * @param {boolean} is_controlled + * @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn + * @param {number} flags + * @param {boolean} apply_transitions + * @returns {void} + */ +function reconcile_indexed_array( + array, + each_block, + dom, + is_controlled, + render_fn, + flags, + apply_transitions +) { + var is_proxied_array = STATE_SYMBOL in array; + var a_blocks = each_block.v; + var active_transitions = each_block.s; + + if (is_proxied_array) { + flags &= ~EACH_ITEM_REACTIVE; + } + + /** @type {number | void} */ + var a = a_blocks.length; + + /** @type {number} */ + var b = array.length; + var length = Math.max(a, b); + var index = 0; + + /** @type {Array<import('./types.js').EachItemBlock>} */ + var b_blocks; + var block; + + if (active_transitions.length !== 0) { + destroy_active_transition_blocks(active_transitions); + } + + if (b === 0) { + b_blocks = []; + // Remove old blocks + if (is_controlled && a !== 0) { + clear_text_content(dom); + } + while (index < length) { + block = a_blocks[index++]; + destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); + } + } else { + var item; + b_blocks = Array(b); + if (current_hydration_fragment !== null) { + /** @type {Node} */ + var hydrating_node = current_hydration_fragment[0]; + for (; index < length; index++) { + // Hydrate block + item = is_proxied_array ? lazy_property(array, index) : array[index]; + var fragment = /** @type {Array<Text | Comment | Element>} */ ( + get_hydration_fragment(hydrating_node) + ); + set_current_hydration_fragment(fragment); + hydrating_node = /** @type {Node} */ ( + /** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling + ); + block = each_item_block(item, null, index, render_fn, flags); + b_blocks[index] = block; + } + } else { + for (; index < length; index++) { + if (index >= a) { + // Add block + item = is_proxied_array ? lazy_property(array, index) : array[index]; + block = each_item_block(item, null, index, render_fn, flags); + b_blocks[index] = block; + insert_each_item_block(block, dom, is_controlled, null); + } else if (index >= b) { + // Remove block + block = a_blocks[index]; + destroy_each_item_block(block, active_transitions, apply_transitions); + } else { + // Update block + item = array[index]; + block = a_blocks[index]; + b_blocks[index] = block; + update_each_item_block(block, item, index, flags); + } + } + } + } + + each_block.v = b_blocks; +} +// Reconcile arrays by the equality of the elements in the array. This algorithm +// is based on Ivi's reconcilation logic: +// +// https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968 +// + +/** + * @template V + * @param {Array<V>} array + * @param {import('./types.js').EachBlock} each_block + * @param {Element | Comment | Text} dom + * @param {boolean} is_controlled + * @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn + * @param {number} flags + * @param {boolean} apply_transitions + * @param {Array<string> | null} keys + * @returns {void} + */ +function reconcile_tracked_array( + array, + each_block, + dom, + is_controlled, + render_fn, + flags, + apply_transitions, + keys +) { + var a_blocks = each_block.v; + const is_computed_key = keys !== null; + var is_proxied_array = STATE_SYMBOL in array; + var active_transitions = each_block.s; + + if (is_proxied_array) { + flags &= ~EACH_ITEM_REACTIVE; + } + + /** @type {number | void} */ + var a = a_blocks.length; + + /** @type {number} */ + var b = array.length; + + /** @type {Array<import('./types.js').EachItemBlock>} */ + var b_blocks; + var block; + + if (active_transitions.length !== 0) { + destroy_active_transition_blocks(active_transitions); + } + + if (b === 0) { + b_blocks = []; + // Remove old blocks + if (is_controlled && a !== 0) { + clear_text_content(dom); + } + while (a > 0) { + block = a_blocks[--a]; + destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); + } + } else { + var a_end = a - 1; + var b_end = b - 1; + var key; + var item; + var idx; + b_blocks = Array(b); + if (current_hydration_fragment !== null) { + var fragment; + + /** @type {Node} */ + var hydrating_node = current_hydration_fragment[0]; + while (b > 0) { + // Hydrate block + idx = b_end - --b; + item = array[idx]; + key = is_computed_key ? keys[idx] : item; + fragment = /** @type {Array<Text | Comment | Element>} */ ( + get_hydration_fragment(hydrating_node) + ); + set_current_hydration_fragment(fragment); + // Get the <!--ssr:..--> tag of the next item in the list + // The fragment array can be empty if each block has no content + hydrating_node = /** @type {Node} */ ( + /** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling + ); + block = each_item_block(item, key, idx, render_fn, flags); + b_blocks[idx] = block; + } + } else if (a === 0) { + // Create new blocks + while (b > 0) { + idx = b_end - --b; + item = array[idx]; + key = is_computed_key ? keys[idx] : item; + block = each_item_block(item, key, idx, render_fn, flags); + b_blocks[idx] = block; + insert_each_item_block(block, dom, is_controlled, null); + } + } else { + var should_update_block = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; + var start = 0; + + /** @type {null | Text | Element | Comment} */ + var sibling = null; + item = array[b_end]; + key = is_computed_key ? keys[b_end] : item; + // Step 1 + outer: while (true) { + // From the end + while (a_blocks[a_end].k === key) { + block = a_blocks[a_end--]; + item = array[b_end]; + if (should_update_block) { + update_each_item_block(block, item, b_end, flags); + } + sibling = get_first_child(block); + b_blocks[b_end] = block; + if (start > --b_end || start > a_end) { + break outer; + } + key = is_computed_key ? keys[b_end] : item; + } + item = array[start]; + key = is_computed_key ? keys[start] : item; + // At the start + while (start <= a_end && start <= b_end && a_blocks[start].k === key) { + item = array[start]; + block = a_blocks[start]; + if (should_update_block) { + update_each_item_block(block, item, start, flags); + } + b_blocks[start] = block; + ++start; + key = is_computed_key ? keys[start] : array[start]; + } + break; + } + // Step 2 + if (start > a_end) { + while (b_end >= start) { + item = array[b_end]; + key = is_computed_key ? keys[b_end] : item; + block = each_item_block(item, key, b_end, render_fn, flags); + b_blocks[b_end--] = block; + sibling = insert_each_item_block(block, dom, is_controlled, sibling); + } + } else if (start > b_end) { + b = start; + do { + if ((block = a_blocks[b++]) !== null) { + destroy_each_item_block(block, active_transitions, apply_transitions); + } + } while (b <= a_end); + } else { + // Step 3 + var pos = 0; + var b_length = b_end - start + 1; + var sources = new Int32Array(b_length); + var item_index = new Map(); + for (b = 0; b < b_length; ++b) { + a = b + start; + sources[b] = NEW_BLOCK; + item = array[a]; + key = is_computed_key ? keys[a] : item; + map_set(item_index, key, a); + } + for (b = start; b <= a_end; ++b) { + a = map_get(item_index, /** @type {V} */ (a_blocks[b].k)); + block = a_blocks[b]; + if (a !== undefined) { + pos = pos < a ? a : MOVED_BLOCK; + sources[a - start] = b; + b_blocks[a] = block; + } else if (block !== null) { + destroy_each_item_block(block, active_transitions, apply_transitions); + } + } + // Step 4 + if (pos === MOVED_BLOCK) { + mark_lis(sources); + } + // If keys are animated, we need to do updates before actual moves + var is_animated = (flags & EACH_IS_ANIMATED) !== 0; + var should_create; + if (is_animated) { + var i = b_length; + while (i-- > 0) { + b_end = i + start; + a = sources[i]; + if (pos === MOVED_BLOCK && a !== LIS_BLOCK) { + block = b_blocks[b_end]; + update_each_item_block(block, item, b_end, flags); + } + } + } + var last_block; + var last_sibling; + while (b_length-- > 0) { + b_end = b_length + start; + a = sources[b_length]; + should_create = a === -1; + item = array[b_end]; + if (should_create) { + key = is_computed_key ? keys[b_end] : item; + block = each_item_block(item, key, b_end, render_fn, flags); + } else { + block = b_blocks[b_end]; + if (!is_animated && should_update_block) { + update_each_item_block(block, item, b_end, flags); + } + } + if (should_create || (pos === MOVED_BLOCK && a !== LIS_BLOCK)) { + last_sibling = last_block === undefined ? sibling : get_first_child(last_block); + sibling = insert_each_item_block(block, dom, is_controlled, last_sibling); + } + b_blocks[b_end] = block; + last_block = block; + } + } + } + } + + each_block.v = b_blocks; +} + +/** + * Longest Increased Subsequence algorithm + * @param {Int32Array} a + * @returns {void} + */ +function mark_lis(a) { + var length = a.length; + var parent = new Int32Array(length); + var index = new Int32Array(length); + var index_length = 0; + var i = 0; + + /** @type {number} */ + var j; + + /** @type {number} */ + var k; + + /** @type {number} */ + var lo; + + /** @type {number} */ + var hi; + + // Skip -1 values at the start of the input array `a`. + for (; a[i] === NEW_BLOCK; ++i) { + /**/ + } + + index[0] = i++; + + for (; i < length; ++i) { + k = a[i]; + + if (k !== NEW_BLOCK) { + // Ignore -1 values. + j = index[index_length]; + + if (a[j] < k) { + parent[i] = j; + index[++index_length] = i; + } else { + lo = 0; + hi = index_length; + + while (lo < hi) { + j = (lo + hi) >> 1; + if (a[index[j]] < k) { + lo = j + 1; + } else { + hi = j; + } + } + + if (k < a[index[lo]]) { + if (lo > 0) { + parent[i] = index[lo - 1]; + } + index[lo] = i; + } + } + } + } + + // Mutate input array `a` and assign -2 value to all nodes that are part of LIS. + j = index[index_length]; + + while (index_length-- >= 0) { + a[j] = LIS_BLOCK; + j = parent[j]; + } +} + +/** + * @param {import('./types.js').Block} block + * @param {Element | Comment | Text} dom + * @param {boolean} is_controlled + * @param {null | Text | Element | Comment} sibling + * @returns {Text | Element | Comment} + */ +function insert_each_item_block(block, dom, is_controlled, sibling) { + var current = /** @type {import('./types.js').TemplateNode} */ (block.d); + + if (sibling === null) { + if (is_controlled) { + return insert(current, /** @type {Element} */ (dom), null); + } else { + return insert(current, /** @type {Element} */ (dom.parentNode), dom); + } + } + + return insert(current, null, sibling); +} + +/** + * @param {import('./types.js').Block} block + * @returns {Text | Element | Comment} + */ +function get_first_child(block) { + var current = block.d; + + if (is_array(current)) { + return /** @type {Text | Element | Comment} */ (current[0]); + } + + return /** @type {Text | Element | Comment} */ (current); +} + +/** + * @param {Array<import('./types.js').EachItemBlock>} active_transitions + * @returns {void} + */ +function destroy_active_transition_blocks(active_transitions) { + var length = active_transitions.length; + + if (length > 0) { + var i = 0; + var block; + var transition; + + for (; i < length; i++) { + block = active_transitions[i]; + transition = block.r; + if (transition !== null) { + block.r = null; + destroy_each_item_block(block, null, false); + } + } + + active_transitions.length = 0; + } +} + +/** + * @param {import('./types.js').Block} block + * @returns {Text | Element | Comment} + */ +function get_first_element(block) { + const current = block.d; + + if (is_array(current)) { + for (let i = 0; i < current.length; i++) { + const node = current[i]; + if (node.nodeType !== 8) { + return node; + } + } + } + + return /** @type {Text | Element | Comment} */ (current); +} + +/** + * @param {import('./types.js').EachItemBlock} block + * @param {any} item + * @param {number} index + * @param {number} type + * @returns {void} + */ +function update_each_item_block(block, item, index, type) { + if ((type & EACH_ITEM_REACTIVE) !== 0) { + set_signal_value(block.v, item); + } else if (is_lazy_property(block.v)) { + block.v.o[block.v.p] = item; + } + const transitions = block.s; + const index_is_reactive = (type & EACH_INDEX_REACTIVE) !== 0; + // Handle each item animations + if (transitions !== null && (type & EACH_KEYED) !== 0) { + let prev_index = block.i; + if (index_is_reactive) { + prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v; + } + const items = block.p.v; + if (prev_index !== index && /** @type {number} */ (index) < items.length) { + const from_dom = /** @type {Element} */ (get_first_element(block)); + const from = from_dom.getBoundingClientRect(); + schedule_task(() => { + trigger_transitions(transitions, 'key', from); + }); + } + } + if (index_is_reactive) { + set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index); + } else { + block.i = index; + } +} + +/** + * @param {import('./types.js').EachItemBlock} block + * @param {null | Array<import('./types.js').Block>} transition_block + * @param {boolean} apply_transitions + * @param {any} controlled + * @returns {void} + */ +export function destroy_each_item_block( + block, + transition_block, + apply_transitions, + controlled = false +) { + const transitions = block.s; + + if (apply_transitions && transitions !== null) { + trigger_transitions(transitions, 'out'); + if (transition_block !== null) { + transition_block.push(block); + } + } else { + const dom = block.d; + if (!controlled && dom !== null) { + remove(dom); + } + destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e)); + } +} + +/** + * @template V + * @template O + * @template P + * @param {V | import('./types.js').LazyProperty<O, P>} item + * @param {unknown} key + * @param {number} index + * @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn + * @param {number} flags + * @returns {import('./types.js').EachItemBlock} + */ +function each_item_block(item, key, index, render_fn, flags) { + const each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0; + + const item_value = each_item_not_reactive + ? item + : (flags & EACH_IS_IMMUTABLE) === 0 + ? mutable_source(item) + : source(item); + + const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); + const block = create_each_item_block(item_value, index_value, key); + + const effect = render_effect( + /** @param {import('./types.js').EachItemBlock} block */ + (block) => { + render_fn(null, block.v, block.i); + }, + block, + true + ); + + block.e = effect; + return block; +} diff --git a/packages/svelte/src/internal/client/hydration.js b/packages/svelte/src/internal/client/hydration.js index dfd850c96..e7643a57c 100644 --- a/packages/svelte/src/internal/client/hydration.js +++ b/packages/svelte/src/internal/client/hydration.js @@ -1,5 +1,7 @@ // Handle hydration +import { schedule_task } from './runtime.js'; + /** @type {null | Array<Text | Comment | Element>} */ export let current_hydration_fragment = null; @@ -68,12 +70,16 @@ export function hydrate_block_anchor(anchor_node, is_controlled) { let fragment = target_node.$$fragment; if (fragment === undefined) { fragment = get_hydration_fragment(target_node); - // @ts-ignore remove to prevent memory leaks - target_node.$$fragment = undefined; + } else { + schedule_task(() => { + // @ts-expect-error clean up memory + target_node.$$fragment = undefined; + }); } set_current_hydration_fragment(fragment); } else { - set_current_hydration_fragment([/** @type {Element} */ (target_node.firstChild)]); + const first_child = /** @type {Element | null} */ (target_node.firstChild); + set_current_hydration_fragment(first_child === null ? [] : [first_child]); } } } diff --git a/packages/svelte/src/internal/client/proxy/proxy.js b/packages/svelte/src/internal/client/proxy/proxy.js new file mode 100644 index 000000000..22e0b959b --- /dev/null +++ b/packages/svelte/src/internal/client/proxy/proxy.js @@ -0,0 +1,176 @@ +import { DEV } from 'esm-env'; +import { + effect_active, + get, + set, + increment, + source, + updating_derived, + UNINITIALIZED +} from '../runtime.js'; +import { define_property, get_descriptor, is_array } from '../utils.js'; +import { READONLY_SYMBOL } from './readonly.js'; + +/** @typedef {{ s: Map<string | symbol, import('../types.js').SourceSignal<any>>; v: import('../types.js').SourceSignal<number>; a: boolean }} Metadata */ +/** @typedef {Record<string | symbol, any> & { [STATE_SYMBOL]: Metadata }} StateObject */ + +export const STATE_SYMBOL = Symbol('$state'); + +const object_prototype = Object.prototype; +const array_prototype = Array.prototype; +const get_prototype_of = Object.getPrototypeOf; +const is_frozen = Object.isFrozen; + +/** + * @template {StateObject} T + * @param {T} value + * @returns {T} + */ +export function proxy(value) { + if (typeof value === 'object' && value != null && !is_frozen(value) && !(STATE_SYMBOL in value)) { + const prototype = get_prototype_of(value); + + // TODO handle Map and Set as well + if (prototype === object_prototype || prototype === array_prototype) { + define_property(value, STATE_SYMBOL, { value: init(value), writable: false }); + + // @ts-expect-error not sure how to fix this + return new Proxy(value, handler); + } + } + + return value; +} + +/** + * @param {StateObject} value + * @returns {Metadata} + */ +function init(value) { + return { + s: new Map(), + v: source(0), + a: is_array(value) + }; +} + +/** @type {ProxyHandler<StateObject>} */ +const handler = { + defineProperty(target, prop, descriptor) { + if (descriptor.value) { + const metadata = target[STATE_SYMBOL]; + + const s = metadata.s.get(prop); + if (s !== undefined) set(s, proxy(descriptor.value)); + } + + return Reflect.defineProperty(target, prop, descriptor); + }, + + deleteProperty(target, prop) { + const metadata = target[STATE_SYMBOL]; + + const s = metadata.s.get(prop); + if (s !== undefined) set(s, UNINITIALIZED); + + if (prop in target) increment(metadata.v); + + return delete target[prop]; + }, + + get(target, prop, receiver) { + if (prop === READONLY_SYMBOL) return target[READONLY_SYMBOL]; + + const metadata = target[STATE_SYMBOL]; + let s = metadata.s.get(prop); + + // if we're reading a property in a reactive context, create a source, + // but only if it's an own property and not a prototype property + if ( + s === undefined && + (effect_active() || updating_derived) && + (!(prop in target) || get_descriptor(target, prop)?.writable) + ) { + s = source(proxy(target[prop])); + metadata.s.set(prop, s); + } + + const value = s !== undefined ? get(s) : Reflect.get(target, prop, receiver); + return value === UNINITIALIZED ? undefined : value; + }, + + getOwnPropertyDescriptor(target, prop) { + const descriptor = Reflect.getOwnPropertyDescriptor(target, prop); + if (descriptor && 'value' in descriptor) { + const metadata = target[STATE_SYMBOL]; + const s = metadata.s.get(prop); + + if (s) { + descriptor.value = get(s); + } + } + + return descriptor; + }, + + has(target, prop) { + if (prop === STATE_SYMBOL) { + return true; + } + const metadata = target[STATE_SYMBOL]; + const has = Reflect.has(target, prop); + + let s = metadata.s.get(prop); + if (s !== undefined || (effect_active() && (!has || get_descriptor(target, prop)?.writable))) { + if (s === undefined) { + s = source(has ? proxy(target[prop]) : UNINITIALIZED); + metadata.s.set(prop, s); + } + const value = get(s); + if (value === UNINITIALIZED) { + return false; + } + } + return has; + }, + + set(target, prop, value) { + if (prop === READONLY_SYMBOL) { + target[READONLY_SYMBOL] = value; + return true; + } + + const metadata = target[STATE_SYMBOL]; + + const s = metadata.s.get(prop); + if (s !== undefined) set(s, proxy(value)); + + if (metadata.a && prop === 'length') { + for (let i = value; i < target.length; i += 1) { + const s = metadata.s.get(i + ''); + if (s !== undefined) set(s, UNINITIALIZED); + } + } + + if (!(prop in target)) increment(metadata.v); + // @ts-ignore + target[prop] = value; + + return true; + }, + + ownKeys(target) { + const metadata = target[STATE_SYMBOL]; + + get(metadata.v); + return Reflect.ownKeys(target); + } +}; + +if (DEV) { + handler.setPrototypeOf = () => { + throw new Error('Cannot set prototype of $state object'); + }; +} + +export { readonly } from './readonly.js'; diff --git a/packages/svelte/src/internal/client/proxy/readonly.js b/packages/svelte/src/internal/client/proxy/readonly.js new file mode 100644 index 000000000..fa698c3b0 --- /dev/null +++ b/packages/svelte/src/internal/client/proxy/readonly.js @@ -0,0 +1,65 @@ +import { define_property, get_descriptor } from '../utils.js'; + +/** + * @template {Record<string | symbol, any>} T + * @typedef {T & { [READONLY_SYMBOL]: Proxy<T> }} StateObject + */ + +export const READONLY_SYMBOL = Symbol('readonly'); + +const object_prototype = Object.prototype; +const array_prototype = Array.prototype; +const get_prototype_of = Object.getPrototypeOf; +const is_frozen = Object.isFrozen; + +/** + * @template {Record<string | symbol, any>} T + * @template {StateObject<T>} U + * @param {U} value + * @returns {Proxy<U> | U} + */ +export function readonly(value) { + const proxy = value && value[READONLY_SYMBOL]; + if (proxy) return proxy; + + if ( + typeof value === 'object' && + value != null && + !is_frozen(value) && + !(READONLY_SYMBOL in value) + ) { + const prototype = get_prototype_of(value); + + // TODO handle Map and Set as well + if (prototype === object_prototype || prototype === array_prototype) { + const proxy = new Proxy(value, handler); + define_property(value, READONLY_SYMBOL, { value: proxy, writable: false }); + + return proxy; + } + } + + return value; +} + +/** @returns {never} */ +const readonly_error = () => { + throw new Error(`Props are read-only, unless used with \`bind:\``); +}; + +/** @type {ProxyHandler<StateObject<any>>} */ +const handler = { + defineProperty: readonly_error, + deleteProperty: readonly_error, + set: readonly_error, + + get(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + + if (!(prop in target)) { + return readonly(value); + } + + return value; + } +}; diff --git a/packages/svelte/src/internal/client/reconciler.js b/packages/svelte/src/internal/client/reconciler.js index a5fcff687..c96d9460a 100644 --- a/packages/svelte/src/internal/client/reconciler.js +++ b/packages/svelte/src/internal/client/reconciler.js @@ -1,17 +1,6 @@ -import { append_child, map_get, map_set, clear_text_content } from './operations.js'; -import { - current_hydration_fragment, - get_hydration_fragment, - hydrate_block_anchor, - set_current_hydration_fragment -} from './hydration.js'; +import { append_child } from './operations.js'; +import { current_hydration_fragment, hydrate_block_anchor } from './hydration.js'; import { is_array } from './utils.js'; -import { each_item_block, destroy_each_item_block, update_each_item_block } from './render.js'; -import { EACH_INDEX_REACTIVE, EACH_IS_ANIMATED, EACH_ITEM_REACTIVE } from '../../constants.js'; - -const NEW_BLOCK = -1; -const MOVED_BLOCK = 99999999; -const LIS_BLOCK = -2; /** @param {string} html */ export function create_fragment_from_html(html) { @@ -103,429 +92,3 @@ export function reconcile_html(dom, value, svg) { target.before(svg ? /** @type {Node} */ (clone.firstChild) : clone); return /** @type {Array<Text | Comment | Element>} */ (frag_nodes); } - -/** - * @param {import('./types.js').Block} block - * @param {Element | Comment | Text} dom - * @param {boolean} is_controlled - * @param {null | Text | Element | Comment} sibling - * @returns {Text | Element | Comment} - */ -function insert_each_item_block(block, dom, is_controlled, sibling) { - var current = /** @type {import('./types.js').TemplateNode} */ (block.d); - if (sibling === null) { - if (is_controlled) { - return insert(current, /** @type {Element} */ (dom), null); - } else { - return insert(current, /** @type {Element} */ (dom.parentNode), dom); - } - } - return insert(current, null, sibling); -} - -/** - * @param {import('./types.js').Block} block - * @returns {Text | Element | Comment} - */ -function get_first_child(block) { - var current = block.d; - if (is_array(current)) { - return /** @type {Text | Element | Comment} */ (current[0]); - } - return /** @type {Text | Element | Comment} */ (current); -} - -/** - * @param {Array<import('./types.js').EachItemBlock>} active_transitions - * @returns {void} - */ -function destroy_active_transition_blocks(active_transitions) { - var length = active_transitions.length; - if (length > 0) { - var i = 0; - var block; - var transition; - for (; i < length; i++) { - block = active_transitions[i]; - transition = block.r; - if (transition !== null) { - block.r = null; - destroy_each_item_block(block, null, false); - } - } - active_transitions.length = 0; - } -} - -/** - * @template V - * @param {Array<V>} array - * @param {import('./types.js').EachBlock} each_block - * @param {Element | Comment | Text} dom - * @param {boolean} is_controlled - * @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn - * @param {number} flags - * @param {boolean} apply_transitions - * @returns {void} - */ -export function reconcile_indexed_array( - array, - each_block, - dom, - is_controlled, - render_fn, - flags, - apply_transitions -) { - var a_blocks = each_block.v; - var active_transitions = each_block.s; - - /** @type {number | void} */ - var a = a_blocks.length; - - /** @type {number} */ - var b = array.length; - var length = Math.max(a, b); - var index = 0; - - /** @type {Array<import('./types.js').EachItemBlock>} */ - var b_blocks; - var block; - if (active_transitions.length !== 0) { - destroy_active_transition_blocks(active_transitions); - } - if (b === 0) { - b_blocks = []; - // Remove old blocks - if (is_controlled && a !== 0) { - clear_text_content(dom); - } - while (index < length) { - block = a_blocks[index++]; - destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); - } - } else { - var item; - b_blocks = Array(b); - if (current_hydration_fragment !== null) { - /** @type {Node} */ - var hydrating_node = current_hydration_fragment[0]; - for (; index < length; index++) { - // Hydrate block - item = array[index]; - var fragment = /** @type {Array<Text | Comment | Element>} */ ( - get_hydration_fragment(hydrating_node) - ); - set_current_hydration_fragment(fragment); - hydrating_node = /** @type {Node} */ ( - /** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling - ); - block = each_item_block(item, null, index, render_fn, flags); - b_blocks[index] = block; - } - } else { - for (; index < length; index++) { - if (index >= a) { - // Add block - item = array[index]; - block = each_item_block(item, null, index, render_fn, flags); - b_blocks[index] = block; - insert_each_item_block(block, dom, is_controlled, null); - } else if (index >= b) { - // Remove block - block = a_blocks[index]; - destroy_each_item_block(block, active_transitions, apply_transitions); - } else { - // Update block - item = array[index]; - block = a_blocks[index]; - b_blocks[index] = block; - update_each_item_block(block, item, index, flags); - } - } - } - } - each_block.v = b_blocks; -} -// Reconcile arrays by the equality of the elements in the array. This algorithm -// is based on Ivi's reconcilation logic: -// -// https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968 -// - -/** - * @template V - * @param {Array<V>} array - * @param {import('./types.js').EachBlock} each_block - * @param {Element | Comment | Text} dom - * @param {boolean} is_controlled - * @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn - * @param {number} flags - * @param {boolean} apply_transitions - * @param {Array<string> | null} keys - * @returns {void} - */ -export function reconcile_tracked_array( - array, - each_block, - dom, - is_controlled, - render_fn, - flags, - apply_transitions, - keys -) { - var a_blocks = each_block.v; - const is_computed_key = keys !== null; - var active_transitions = each_block.s; - - /** @type {number | void} */ - var a = a_blocks.length; - - /** @type {number} */ - var b = array.length; - - /** @type {Array<import('./types.js').EachItemBlock>} */ - var b_blocks; - var block; - if (active_transitions.length !== 0) { - destroy_active_transition_blocks(active_transitions); - } - if (b === 0) { - b_blocks = []; - // Remove old blocks - if (is_controlled && a !== 0) { - clear_text_content(dom); - } - while (a > 0) { - block = a_blocks[--a]; - destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); - } - } else { - var a_end = a - 1; - var b_end = b - 1; - var key; - var item; - var idx; - b_blocks = Array(b); - if (current_hydration_fragment !== null) { - var fragment; - - /** @type {Node} */ - var hydrating_node = current_hydration_fragment[0]; - while (b > 0) { - // Hydrate block - idx = b_end - --b; - item = array[idx]; - key = is_computed_key ? keys[idx] : item; - fragment = /** @type {Array<Text | Comment | Element>} */ ( - get_hydration_fragment(hydrating_node) - ); - set_current_hydration_fragment(fragment); - // Get the <!--ssr:..--> tag of the next item in the list - // The fragment array can be empty if each block has no content - hydrating_node = /** @type {Node} */ ( - /** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling - ); - block = each_item_block(item, key, idx, render_fn, flags); - b_blocks[idx] = block; - } - } else if (a === 0) { - // Create new blocks - while (b > 0) { - idx = b_end - --b; - item = array[idx]; - key = is_computed_key ? keys[idx] : item; - block = each_item_block(item, key, idx, render_fn, flags); - b_blocks[idx] = block; - insert_each_item_block(block, dom, is_controlled, null); - } - } else { - var should_update_block = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; - var start = 0; - - /** @type {null | Text | Element | Comment} */ - var sibling = null; - item = array[b_end]; - key = is_computed_key ? keys[b_end] : item; - // Step 1 - outer: while (true) { - // From the end - while (a_blocks[a_end].k === key) { - block = a_blocks[a_end--]; - item = array[b_end]; - if (should_update_block) { - update_each_item_block(block, item, b_end, flags); - } - sibling = get_first_child(block); - b_blocks[b_end] = block; - if (start > --b_end || start > a_end) { - break outer; - } - key = is_computed_key ? keys[b_end] : item; - } - item = array[start]; - key = is_computed_key ? keys[start] : item; - // At the start - while (start <= a_end && start <= b_end && a_blocks[start].k === key) { - item = array[start]; - block = a_blocks[start]; - if (should_update_block) { - update_each_item_block(block, item, start, flags); - } - b_blocks[start] = block; - ++start; - key = is_computed_key ? keys[start] : array[start]; - } - break; - } - // Step 2 - if (start > a_end) { - while (b_end >= start) { - item = array[b_end]; - key = is_computed_key ? keys[b_end] : item; - block = each_item_block(item, key, b_end, render_fn, flags); - b_blocks[b_end--] = block; - sibling = insert_each_item_block(block, dom, is_controlled, sibling); - } - } else if (start > b_end) { - b = start; - do { - if ((block = a_blocks[b++]) !== null) { - destroy_each_item_block(block, active_transitions, apply_transitions); - } - } while (b <= a_end); - } else { - // Step 3 - var pos = 0; - var b_length = b_end - start + 1; - var sources = new Int32Array(b_length); - var item_index = new Map(); - for (b = 0; b < b_length; ++b) { - a = b + start; - sources[b] = NEW_BLOCK; - item = array[a]; - key = is_computed_key ? keys[a] : item; - map_set(item_index, key, a); - } - for (b = start; b <= a_end; ++b) { - a = map_get(item_index, /** @type {V} */ (a_blocks[b].k)); - block = a_blocks[b]; - if (a !== undefined) { - pos = pos < a ? a : MOVED_BLOCK; - sources[a - start] = b; - b_blocks[a] = block; - } else if (block !== null) { - destroy_each_item_block(block, active_transitions, apply_transitions); - } - } - // Step 4 - if (pos === MOVED_BLOCK) { - mark_lis(sources); - } - // If keys are animated, we need to do updates before actual moves - var is_animated = (flags & EACH_IS_ANIMATED) !== 0; - var should_create; - if (is_animated) { - var i = b_length; - while (i-- > 0) { - b_end = i + start; - a = sources[i]; - if (pos === MOVED_BLOCK && a !== LIS_BLOCK) { - block = b_blocks[b_end]; - update_each_item_block(block, item, b_end, flags); - } - } - } - var last_block; - var last_sibling; - while (b_length-- > 0) { - b_end = b_length + start; - a = sources[b_length]; - should_create = a === -1; - item = array[b_end]; - if (should_create) { - key = is_computed_key ? keys[b_end] : item; - block = each_item_block(item, key, b_end, render_fn, flags); - } else { - block = b_blocks[b_end]; - if (!is_animated && should_update_block) { - update_each_item_block(block, item, b_end, flags); - } - } - if (should_create || (pos === MOVED_BLOCK && a !== LIS_BLOCK)) { - last_sibling = last_block === undefined ? sibling : get_first_child(last_block); - sibling = insert_each_item_block(block, dom, is_controlled, last_sibling); - } - b_blocks[b_end] = block; - last_block = block; - } - } - } - } - each_block.v = b_blocks; -} -// Longest Increased Subsequence algorithm. - -/** - * @param {Int32Array} a - * @returns {void} - */ -function mark_lis(a) { - var length = a.length; - var parent = new Int32Array(length); - var index = new Int32Array(length); - var index_length = 0; - var i = 0; - - /** @type {number} */ - var j; - - /** @type {number} */ - var k; - - /** @type {number} */ - var lo; - - /** @type {number} */ - var hi; - // Skip -1 values at the start of the input array `a`. - for (; a[i] === NEW_BLOCK; ++i) { - /**/ - } - index[0] = i++; - for (; i < length; ++i) { - k = a[i]; - if (k !== NEW_BLOCK) { - // Ignore -1 values. - j = index[index_length]; - if (a[j] < k) { - parent[i] = j; - index[++index_length] = i; - } else { - lo = 0; - hi = index_length; - while (lo < hi) { - j = (lo + hi) >> 1; - if (a[index[j]] < k) { - lo = j + 1; - } else { - hi = j; - } - } - if (k < a[index[lo]]) { - if (lo > 0) { - parent[i] = index[lo - 1]; - } - index[lo] = i; - } - } - } - } - // Mutate input array `a` and assign -2 value to all nodes that are part of LIS. - j = index[index_length]; - while (index_length-- >= 0) { - a[j] = LIS_BLOCK; - j = parent[j]; - } -} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 804840d6c..2111f412e 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -11,8 +11,6 @@ import { } from './operations.js'; import { create_root_block, - create_each_item_block, - create_each_block, create_if_block, create_key_block, create_await_block, @@ -21,22 +19,8 @@ import { create_dynamic_component_block, create_snippet_block } from './block.js'; -import { - EACH_KEYED, - EACH_IS_CONTROLLED, - EACH_INDEX_REACTIVE, - EACH_ITEM_REACTIVE, - PassiveDelegatedEvents, - DelegatedEvents -} from '../../constants.js'; -import { - create_fragment_from_html, - insert, - reconcile_tracked_array, - reconcile_html, - remove, - reconcile_indexed_array -} from './reconciler.js'; +import { PassiveDelegatedEvents, DelegatedEvents, AttributeAliases } from '../../constants.js'; +import { create_fragment_from_html, insert, reconcile_html, remove } from './reconciler.js'; import { render_effect, destroy_signal, @@ -53,14 +37,13 @@ import { expose, safe_not_equal, current_block, - set_signal_value, source, managed_effect, - safe_equal, push, current_component_context, pop, - schedule_task + unwrap, + mutable_source } from './runtime.js'; import { current_hydration_fragment, @@ -133,7 +116,7 @@ export function svg_replace(node) { * @param {boolean} is_fragment * @param {boolean} use_clone_node * @param {null | Text | Comment | Element} anchor - * @param {() => Element} [template_element_fn] + * @param {() => Node} [template_element_fn] * @returns {Element | DocumentFragment | Node[]} */ function open_template(is_fragment, use_clone_node, anchor, template_element_fn) { @@ -156,7 +139,7 @@ function open_template(is_fragment, use_clone_node, anchor, template_element_fn) /** * @param {null | Text | Comment | Element} anchor * @param {boolean} use_clone_node - * @param {() => Element} [template_element_fn] + * @param {() => Node} [template_element_fn] * @returns {Element | DocumentFragment | Node[]} */ /*#__NO_SIDE_EFFECTS__*/ @@ -167,7 +150,7 @@ export function open(anchor, use_clone_node, template_element_fn) { /** * @param {null | Text | Comment | Element} anchor * @param {boolean} use_clone_node - * @param {() => Element} [template_element_fn] + * @param {() => Node} [template_element_fn] * @returns {Element | DocumentFragment | Node[]} */ /*#__NO_SIDE_EFFECTS__*/ @@ -175,6 +158,25 @@ export function open_frag(anchor, use_clone_node, template_element_fn) { return open_template(true, use_clone_node, anchor, template_element_fn); } +const space_template = template(' ', false); +const comment_template = template('<!>', true); + +/** + * @param {null | Text | Comment | Element} anchor + */ +/*#__NO_SIDE_EFFECTS__*/ +export function space(anchor) { + return open(anchor, true, space_template); +} + +/** + * @param {null | Text | Comment | Element} anchor + */ +/*#__NO_SIDE_EFFECTS__*/ +export function comment(anchor) { + return open_frag(anchor, true, comment_template); +} + /** * @param {Element | Text} dom * @param {boolean} is_fragment @@ -190,8 +192,10 @@ function close_template(dom, is_fragment, anchor) { ? dom : /** @type {import('./types.js').TemplateNode[]} */ (Array.from(dom.childNodes)) : dom; - if (anchor !== null && current_hydration_fragment === null) { - insert(current, null, anchor); + if (anchor !== null) { + if (current_hydration_fragment === null) { + insert(current, null, anchor); + } } block.d = current; } @@ -1205,14 +1209,21 @@ export function bind_prop(props, prop, value) { /** * @param {Element} element_or_component * @param {(value: unknown) => void} update + * @param {import('./types.js').MaybeSignal} binding * @returns {void} */ -export function bind_this(element_or_component, update) { +export function bind_this(element_or_component, update, binding) { untrack(() => { update(element_or_component); render_effect(() => () => { - untrack(() => { - update(null); + // Defer to the next tick so that all updates can be reconciled first. + // This solves the case where one variable is shared across multiple this-bindings. + render_effect(() => { + untrack(() => { + if (!is_signal(binding) || binding.v === element_or_component) { + update(null); + } + }); }); }); }); @@ -1368,9 +1379,7 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) { } else if (current_hydration_fragment !== null) { const comment_text = /** @type {Comment} */ (current_hydration_fragment?.[0])?.data; if ( - (!comment_text && - // Can happen when a svelte:element that is turned into a void element has an if block inside - current_hydration_fragment[0] !== null) || + !comment_text || (comment_text === 'ssr:if:true' && !result) || (comment_text === 'ssr:if:false' && result) ) { @@ -1514,7 +1523,7 @@ function swap_block_dom(block, from, to) { /** * @param {Comment} anchor_node * @param {() => string} tag_fn - * @param {null | ((element: Element, anchor: Node) => void)} render_fn + * @param {undefined | ((element: Element, anchor: Node) => void)} render_fn * @param {any} is_svg * @returns {void} */ @@ -1554,7 +1563,7 @@ export function element(anchor_node, tag_fn, render_fn, is_svg = false) { block.d = null; } element = next_element; - if (element !== null && render_fn !== null) { + if (element !== null && render_fn !== undefined) { let anchor; if (current_hydration_fragment !== null) { // Use the existing ssr comment as the anchor so that the inner open and close @@ -1992,288 +2001,6 @@ export function key(anchor_node, key, render_fn) { block.e = key_effect; } -/** - * @param {import('./types.js').Block} block - * @returns {Text | Element | Comment} - */ -function get_first_element(block) { - const current = block.d; - if (is_array(current)) { - for (let i = 0; i < current.length; i++) { - const node = current[i]; - if (node.nodeType !== 8) { - return node; - } - } - } - return /** @type {Text | Element | Comment} */ (current); -} - -/** - * @param {import('./types.js').EachItemBlock} block - * @param {any} item - * @param {number} index - * @param {number} type - * @returns {void} - */ -export function update_each_item_block(block, item, index, type) { - if ((type & EACH_ITEM_REACTIVE) !== 0) { - set_signal_value(block.v, item); - } - const transitions = block.s; - const index_is_reactive = (type & EACH_INDEX_REACTIVE) !== 0; - // Handle each item animations - if (transitions !== null && (type & EACH_KEYED) !== 0) { - let prev_index = block.i; - if (index_is_reactive) { - prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v; - } - const items = block.p.v; - if (prev_index !== index && /** @type {number} */ (index) < items.length) { - const from_dom = /** @type {Element} */ (get_first_element(block)); - const from = from_dom.getBoundingClientRect(); - schedule_task(() => { - trigger_transitions(transitions, 'key', from); - }); - } - } - if (index_is_reactive) { - set_signal_value(/** @type {import('./types.js').Signal<number>} */ (block.i), index); - } else { - block.i = index; - } -} - -/** - * @param {import('./types.js').EachItemBlock} block - * @param {null | Array<import('./types.js').Block>} transition_block - * @param {boolean} apply_transitions - * @param {any} controlled - * @returns {void} - */ -export function destroy_each_item_block( - block, - transition_block, - apply_transitions, - controlled = false -) { - const transitions = block.s; - if (apply_transitions && transitions !== null) { - trigger_transitions(transitions, 'out'); - if (transition_block !== null) { - transition_block.push(block); - } - } else { - const dom = block.d; - if (!controlled && dom !== null) { - remove(dom); - } - destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e)); - } -} - -/** - * @template V - * @param {V} item - * @param {unknown} key - * @param {number} index - * @param {(anchor: null, item: V, index: number | import('./types.js').Signal<number>) => void} render_fn - * @param {number} flags - * @returns {import('./types.js').EachItemBlock} - */ -export function each_item_block(item, key, index, render_fn, flags) { - const item_value = (flags & EACH_ITEM_REACTIVE) === 0 ? item : source(item); - const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); - const block = create_each_item_block(item_value, index_value, key); - const effect = render_effect( - /** @param {import('./types.js').EachItemBlock} block */ - (block) => { - render_fn(null, block.v, block.i); - }, - block, - true - ); - block.e = effect; - return block; -} - -/** - * @template V - * @param {Element | Comment} anchor_node - * @param {() => V[]} collection - * @param {number} flags - * @param {null | ((item: V) => string)} key_fn - * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn - * @param {null | ((anchor: Node) => void)} fallback_fn - * @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn - * @returns {void} - */ -function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_fn) { - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - const block = create_each_block(flags, anchor_node); - - /** @type {null | import('./types.js').Render} */ - let current_fallback = null; - hydrate_block_anchor(anchor_node, is_controlled); - - /** @type {V[]} */ - let array; - - /** @type {Array<string> | null} */ - let keys = null; - - /** @type {null | import('./types.js').EffectSignal} */ - let render = null; - block.r = - /** @param {import('./types.js').Transition} transition */ - (transition) => { - const fallback = /** @type {import('./types.js').Render} */ (current_fallback); - const transitions = fallback.s; - transitions.add(transition); - transition.f(() => { - transitions.delete(transition); - if (transitions.size === 0) { - if (fallback.e !== null) { - if (fallback.d !== null) { - remove(fallback.d); - fallback.d = null; - } - destroy_signal(fallback.e); - fallback.e = null; - } - } - }); - }; - const create_fallback_effect = () => { - /** @type {import('./types.js').Render} */ - const fallback = { - d: null, - e: null, - s: new Set(), - p: current_fallback - }; - // Managed effect - const effect = render_effect( - () => { - const dom = block.d; - if (dom !== null) { - remove(dom); - block.d = null; - } - let anchor = block.a; - const is_controlled = (block.f & EACH_IS_CONTROLLED) !== 0; - if (is_controlled) { - anchor = empty(); - block.a.appendChild(anchor); - } - /** @type {(anchor: Node) => void} */ (fallback_fn)(anchor); - fallback.d = block.d; - block.d = null; - }, - block, - true - ); - fallback.e = effect; - current_fallback = fallback; - }; - const each = render_effect( - () => { - /** @type {V[]} */ - const maybe_array = collection(); - array = is_array(maybe_array) - ? maybe_array - : maybe_array == null - ? [] - : Array.from(maybe_array); - if (key_fn !== null) { - keys = array.map(key_fn); - } - if (fallback_fn !== null) { - if (array.length === 0) { - if (block.v.length !== 0 || render === null) { - create_fallback_effect(); - } - } else if (block.v.length === 0 && current_fallback !== null) { - const fallback = current_fallback; - const transitions = fallback.s; - if (transitions.size === 0) { - if (fallback.d !== null) { - remove(fallback.d); - fallback.d = null; - } - } else { - trigger_transitions(transitions, 'out'); - } - } - } - if (render !== null) { - execute_effect(render); - } - }, - block, - false - ); - render = render_effect( - /** @param {import('./types.js').EachBlock} block */ - (block) => { - const flags = block.f; - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - const anchor_node = block.a; - reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys); - }, - block, - true - ); - push_destroy_fn(each, () => { - const flags = block.f; - const anchor_node = block.a; - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - let fallback = current_fallback; - while (fallback !== null) { - const dom = fallback.d; - if (dom !== null) { - remove(dom); - } - const effect = fallback.e; - if (effect !== null) { - destroy_signal(effect); - } - fallback = fallback.p; - } - // Clear the array - reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys); - destroy_signal(/** @type {import('./types.js').EffectSignal} */ (render)); - }); - block.e = each; -} - -/** - * @template V - * @param {Element | Comment} anchor_node - * @param {() => V[]} collection - * @param {number} flags - * @param {null | ((item: V) => string)} key_fn - * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn - * @param {null | ((anchor: Node) => void)} fallback_fn - * @returns {void} - */ -export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) { - each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_tracked_array); -} - -/** - * @template V - * @param {Element | Comment} anchor_node - * @param {() => V[]} collection - * @param {number} flags - * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal<number>) => void} render_fn - * @param {null | ((anchor: Node) => void)} fallback_fn - * @returns {void} - */ -export function each_indexed(anchor_node, collection, flags, render_fn, fallback_fn) { - each(anchor_node, collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array); -} - /** * @param {Element | Text | Comment} anchor * @param {boolean} is_html @@ -2504,6 +2231,7 @@ export function attr(dom, attribute, value) { // (we can't just compare the strings as they can be different between client and server but result in the // same url, so we would need to create hidden anchor elements to compare them) attribute !== 'src' && + attribute !== 'href' && attribute !== 'srcset') ) { if (value === null) { @@ -2522,7 +2250,7 @@ let src_url_equal_anchor; * @param {string} url * @returns {boolean} */ -export function src_url_equal(element_src, url) { +function src_url_equal(element_src, url) { if (element_src === url) return true; if (!src_url_equal_anchor) { src_url_equal_anchor = document.createElement('a'); @@ -2538,13 +2266,13 @@ function split_srcset(srcset) { } /** - * @param {HTMLSourceElement | HTMLImageElement} element_srcset + * @param {HTMLSourceElement | HTMLImageElement} element * @param {string | undefined | null} srcset * @returns {boolean} */ -export function srcset_url_equal(element_srcset, srcset) { - const element_urls = split_srcset(element_srcset.srcset); - const urls = split_srcset(srcset || ''); +export function srcset_url_equal(element, srcset) { + const element_urls = split_srcset(element.srcset); + const urls = split_srcset(srcset ?? ''); return ( urls.length === element_urls.length && @@ -2567,22 +2295,20 @@ export function srcset_url_equal(element_srcset, srcset) { * @param {string | null} value */ function check_src_in_dev_hydration(dom, attribute, value) { - if (current_hydration_fragment !== null && (attribute === 'src' || attribute === 'srcset')) { - if ( - (attribute === 'src' && !src_url_equal(dom.getAttribute('src') || '', value || '')) || - (attribute === 'srcset' && - !srcset_url_equal(/** @type {HTMLImageElement | HTMLSourceElement} */ (dom), value || '')) - ) { - // eslint-disable-next-line no-console - console.error( - 'Detected a src/srcset attribute value change during hydration. This will not be repaired during hydration, ' + - 'the src/srcset value that came from the server will be used. Related element:', - dom, - ' Differing value:', - value - ); - } - } + if (!current_hydration_fragment) return; + if (attribute !== 'src' && attribute !== 'href' && attribute !== 'srcset') return; + + if (attribute === 'srcset' && srcset_url_equal(dom, value)) return; + if (src_url_equal(dom.getAttribute(attribute) ?? '', value ?? '')) return; + + // eslint-disable-next-line no-console + console.error( + `Detected a ${attribute} attribute value change during hydration. This will not be repaired during hydration, ` + + `the ${attribute} value that came from the server will be used. Related element:`, + dom, + ' Differing value:', + value + ); } /** @@ -2675,10 +2401,11 @@ function get_setters(element) { * @param {Element & ElementCSSInlineStyle} dom * @param {Record<string, unknown> | null} prev * @param {Record<string, unknown>[]} attrs + * @param {boolean} lowercase_attributes * @param {string} css_hash * @returns {Record<string, unknown>} */ -export function spread_attributes(dom, prev, attrs, css_hash) { +export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_hash) { const next = Object.assign({}, ...attrs); const has_hash = css_hash.length !== 0; for (const key in prev) { @@ -2697,13 +2424,13 @@ export function spread_attributes(dom, prev, attrs, css_hash) { let value = next[key]; if (value === prev?.[key]) continue; - const prefix = key.slice(0, 2); + const prefix = key[0] + key[1]; // this is faster than key.slice(0, 2) if (prefix === '$$') continue; if (prefix === 'on') { /** @type {{ capture?: true }} */ const opts = {}; - let event_name = key.slice(2).toLowerCase(); + let event_name = key.slice(2); const delegated = DelegatedEvents.includes(event_name); if ( @@ -2735,25 +2462,33 @@ export function spread_attributes(dom, prev, attrs, css_hash) { } else if (key === '__value' || key === 'value') { // @ts-ignore dom.value = dom[key] = dom.__value = value; - } else if (setters.includes(key)) { - if (DEV) { - check_src_in_dev_hydration(dom, key, value); - } - if ( - current_hydration_fragment === null || - // @ts-ignore see attr method for an explanation of src/srcset - (dom[key] !== value && key !== 'src' && key !== 'srcset') - ) { - // @ts-ignore - dom[key] = value; - } - } else if (typeof value !== 'function') { - if (has_hash && key === 'class') { - if (value) value += ' '; - value += css_hash; + } else { + let name = key; + if (lowercase_attributes) { + name = name.toLowerCase(); + name = AttributeAliases[name] || name; } - attr(dom, key, value); + if (setters.includes(name)) { + if (DEV) { + check_src_in_dev_hydration(dom, name, value); + } + if ( + current_hydration_fragment === null || + // @ts-ignore see attr method for an explanation of src/srcset + (dom[name] !== value && name !== 'src' && name !== 'href' && name !== 'srcset') + ) { + // @ts-ignore + dom[name] = value; + } + } else if (typeof value !== 'function') { + if (has_hash && name === 'class') { + if (value) value += ' '; + value += css_hash; + } + + attr(dom, name, value); + } } } return next; @@ -2784,6 +2519,7 @@ export function spread_dynamic_element_attributes(node, prev, attrs, css_hash) { /** @type {Element & ElementCSSInlineStyle} */ (node), prev, attrs, + node.namespaceURI !== 'http://www.w3.org/2000/svg', css_hash ); } @@ -2854,20 +2590,6 @@ export function spread_props(props) { return merged_props; } -/** - * @template V - * @param {V} value - * @returns {import('./types.js').UnwrappedSignal<V>} - */ -export function unwrap(value) { - if (is_signal(value)) { - // @ts-ignore - return get(value); - } - // @ts-ignore - return value; -} - /** * Mounts the given component to the given target and returns a handle to the component's public accessors * as well as a `$set` and `$destroy` method to update the props of the component or destroy it. @@ -2877,13 +2599,13 @@ export function unwrap(value) { * @template {Record<string, any>} Props * @template {Record<string, any> | undefined} Exports * @template {Record<string, any>} Events - * @param {import('../../main/public.js').SvelteComponent<Props, Events>} component + * @param {typeof import('../../main/public.js').SvelteComponent<Props, Events>} component * @param {{ * target: Node; * props?: Props; + * events?: Events; * context?: Map<any, any>; * intro?: boolean; - * immutable?: boolean; * recover?: false; * }} options * @returns {Exports & { $destroy: () => void; $set: (props: Partial<Props>) => void; }} @@ -2901,15 +2623,7 @@ export function createRoot(component, options) { * @param {any} value */ function add_prop(name, value) { - const prop = source( - value, - options.immutable - ? /** - * @param {any} a - * @param {any} b - */ (a, b) => a === b - : safe_equal - ); + const prop = source(value); _sources[name] = prop; define_property(_props, name, { get() { @@ -2943,11 +2657,9 @@ export function createRoot(component, options) { return _props[property]; } }); - const props_source = source( - props_proxy, - // We're resetting the same proxy instance for updates, therefore bypass equality checks - () => false - ); + + // We're resetting the same proxy instance for updates, therefore bypass equality checks + const props_source = mutable_source(props_proxy); let [accessors, $destroy] = mount(component, { ...options, @@ -2991,19 +2703,18 @@ export function createRoot(component, options) { /** * Mounts the given component to the given target and returns the accessors of the component and a function to destroy it. * - * If you need to interact with the component after mounting, use `create` instead. + * If you need to interact with the component after mounting, use `createRoot` instead. * * @template {Record<string, any>} Props * @template {Record<string, any> | undefined} Exports * @template {Record<string, any>} Events - * @param {import('../../main/public.js').SvelteComponent<Props, Events>} component + * @param {typeof import('../../main/public.js').SvelteComponent<Props, Events>} component * @param {{ * target: Node; * props?: Props; * events?: Events; * context?: Map<any, any>; * intro?: boolean; - * immutable?: boolean; * recover?: false; * }} options * @returns {[Exports, () => void]} @@ -3132,13 +2843,18 @@ export function sanitize_slots(props) { } /** - * @param {() => void} create_snippet + * @param {() => Function} get_snippet + * @param {Node} node + * @param {() => any} args * @returns {void} */ -export function snippet_effect(create_snippet) { +export function snippet_effect(get_snippet, node, args) { const block = create_snippet_block(); render_effect(() => { - create_snippet(); + // Only rerender when the snippet function itself changes, + // not when an eagerly-read prop inside the snippet function changes + const snippet = get_snippet(); + untrack(() => snippet(node, args)); return () => { if (block.d !== null) { remove(block.d); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index a082adda2..2d3e5204c 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1,8 +1,7 @@ import { DEV } from 'esm-env'; import { subscribe_to_store } from '../../store/utils.js'; import { EMPTY_FUNC, run_all } from '../common.js'; -import { unwrap } from './render.js'; -import { is_array } from './utils.js'; +import { get_descriptors, is_array } from './utils.js'; export const SOURCE = 1; export const DERIVED = 1 << 1; @@ -24,6 +23,7 @@ const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; export const UNINITIALIZED = Symbol(); +export const LAZY_PROPERTY = Symbol(); // Used for controlling the flush of effects. let current_scheduler_mode = FLUSH_MICROTASK; @@ -54,6 +54,8 @@ export let current_effect = null; /** @type {null | import('./types.js').Signal[]} */ let current_dependencies = null; let current_dependencies_index = 0; +/** @type {null | import('./types.js').Signal[]} */ +let current_untracked_writes = null; // Handling capturing of signals from object property getters let current_should_capture_signal = false; /** If `true`, `get`ting the signal should not register it as a dependency */ @@ -69,8 +71,14 @@ let current_skip_consumer = false; // Handle collecting all signals which are read during a specific time frame let is_signals_recorded = false; let captured_signals = new Set(); -// Handle rendering tree blocks and anchors +/** @type {Function | null} */ +let inspect_fn = null; + +/** @type {Array<import('./types.js').SourceSignal & import('./types.js').SourceSignalDebug>} */ +let inspect_captured_signals = []; + +// Handle rendering tree blocks and anchors /** @type {null | import('./types.js').Block} */ export let current_block = null; // Handling runtime component context @@ -79,6 +87,8 @@ export let current_block = null; export let current_component_context = null; export let is_ssr = false; +export let updating_derived = false; + /** * @param {boolean} ssr * @returns {void} @@ -100,8 +110,6 @@ export function create_component_context(props) { c: null, // effects e: null, - // immutable - i: false, // mounted m: false, // parent @@ -137,7 +145,7 @@ export function set_current_component_context(context_stack_item) { * @param {unknown} b * @returns {boolean} */ -function default_equals(a, b) { +export function default_equals(a, b) { return a === b; } @@ -145,14 +153,30 @@ function default_equals(a, b) { * @template V * @param {import('./types.js').SignalFlags} flags * @param {V} value - * @returns {import('./types.js').SourceSignal<V>} + * @returns {import('./types.js').SourceSignal<V> | import('./types.js').SourceSignal<V> & import('./types.js').SourceSignalDebug} */ function create_source_signal(flags, value) { - const source = { + if (DEV) { + return { + // consumers + c: null, + // equals + e: default_equals, + // flags + f: flags, + // value + v: value, + // context: We can remove this if we get rid of beforeUpdate/afterUpdate + x: null, + // this is for DEV only + inspect: new Set() + }; + } + return { // consumers c: null, // equals - e: null, + e: default_equals, // flags f: flags, // value @@ -160,7 +184,6 @@ function create_source_signal(flags, value) { // context: We can remove this if we get rid of beforeUpdate/afterUpdate x: null }; - return source; } /** @@ -168,9 +191,36 @@ function create_source_signal(flags, value) { * @param {import('./types.js').SignalFlags} flags * @param {V} value * @param {import('./types.js').Block | null} block - * @returns {import('./types.js').ComputationSignal<V>} + * @returns {import('./types.js').ComputationSignal<V> | import('./types.js').ComputationSignal<V> & import('./types.js').SourceSignalDebug} */ function create_computation_signal(flags, value, block) { + if (DEV) { + return { + // block + b: block, + // consumers + c: null, + // destroy + d: null, + // equals + e: null, + // flags + f: flags, + // init + i: null, + // references + r: null, + // value + v: value, + // context: We can remove this if we get rid of beforeUpdate/afterUpdate + x: null, + // destroy + y: null, + // this is for DEV only + inspect: new Set() + }; + } + return { // block b: block, @@ -261,6 +311,7 @@ function execute_signal_fn(signal) { const init = signal.i; const previous_dependencies = current_dependencies; const previous_dependencies_index = current_dependencies_index; + const previous_untracked_writes = current_untracked_writes; const previous_consumer = current_consumer; const previous_block = current_block; const previous_component_context = current_component_context; @@ -269,6 +320,7 @@ function execute_signal_fn(signal) { const previous_untracking = current_untracking; current_dependencies = /** @type {null | import('./types.js').Signal[]} */ (null); current_dependencies_index = 0; + current_untracked_writes = null; current_consumer = signal; current_block = signal.b; current_component_context = signal.x; @@ -326,6 +378,7 @@ function execute_signal_fn(signal) { } finally { current_dependencies = previous_dependencies; current_dependencies_index = previous_dependencies_index; + current_untracked_writes = previous_untracked_writes; current_consumer = previous_consumer; current_block = previous_block; current_component_context = previous_component_context; @@ -448,6 +501,19 @@ export function execute_effect(signal) { } } +function infinite_loop_guard() { + if (flush_count > 100) { + throw new Error( + 'ERR_SVELTE_TOO_MANY_UPDATES' + + (DEV + ? ': Maximum update depth exceeded. This can happen when a reactive block or effect ' + + 'repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops.' + : '') + ); + } + flush_count++; +} + /** * @param {Array<import('./types.js').EffectSignal>} effects * @returns {void} @@ -455,16 +521,7 @@ export function execute_effect(signal) { function flush_queued_effects(effects) { const length = effects.length; if (length > 0) { - if (flush_count > 100) { - throw new Error( - 'ERR_SVELTE_TOO_MANY_UPDATES' + - (DEV - ? ': Maximum update depth exceeded. This can happen when a reactive block or effect ' + - 'repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops.' - : '') - ); - } - flush_count++; + infinite_loop_guard(); let i; for (i = 0; i < length; i++) { const signal = effects[i]; @@ -585,13 +642,13 @@ export function flushSync(fn) { const previous_queued_pre_and_render_effects = current_queued_pre_and_render_effects; const previous_queued_effects = current_queued_effects; try { + infinite_loop_guard(); /** @type {import('./types.js').EffectSignal[]} */ const pre_and_render_effects = []; /** @type {import('./types.js').EffectSignal[]} */ const effects = []; current_scheduler_mode = FLUSH_SYNC; - flush_count = 0; current_queued_pre_and_render_effects = pre_and_render_effects; current_queued_effects = effects; flush_queued_effects(previous_queued_pre_and_render_effects); @@ -605,6 +662,7 @@ export function flushSync(fn) { if (is_task_queued) { process_task(); } + flush_count = 0; } finally { current_scheduler_mode = previous_scheduler_mode; current_queued_pre_and_render_effects = previous_queued_pre_and_render_effects; @@ -630,7 +688,10 @@ export async function tick() { * @returns {void} */ function update_derived(signal, force_schedule) { + const previous_updating_derived = updating_derived; + updating_derived = true; const value = execute_signal_fn(signal); + updating_derived = previous_updating_derived; const status = current_skip_consumer || (current_effect === null && (signal.f & UNOWNED) !== 0) ? DIRTY @@ -640,6 +701,12 @@ function update_derived(signal, force_schedule) { if (!equals(value, signal.v)) { signal.v = value; mark_signal_consumers(signal, DIRTY, force_schedule); + + // @ts-expect-error + if (DEV && signal.inspect && force_schedule) { + // @ts-expect-error + for (const fn of signal.inspect) fn(); + } } } @@ -662,7 +729,7 @@ export function store_get(store, store_name, stores) { entry = { store: null, last_value: null, - value: source(UNINITIALIZED), + value: mutable_source(UNINITIALIZED), unsubscribe: EMPTY_FUNC }; // TODO: can we remove this code? it was refactored out when we split up source/comptued signals @@ -688,7 +755,7 @@ export function store_get(store, store_name, stores) { /** * @template V * @param {import('./types.js').Store<V> | null | undefined} store - * @param {import('./types.js').Signal<V>} source + * @param {import('./types.js').SourceSignal<V>} source */ function connect_store_to_signal(store, source) { if (store == null) { @@ -756,6 +823,14 @@ export function exposable(fn) { * @returns {V} */ export function get(signal) { + // @ts-expect-error + if (DEV && signal.inspect && inspect_fn) { + // @ts-expect-error + signal.inspect.add(inspect_fn); + // @ts-expect-error + inspect_captured_signals.push(signal); + } + const flags = signal.f; if ((flags & DESTROYED) !== 0) { return signal.v; @@ -785,10 +860,27 @@ export function get(signal) { } else if (signal !== current_dependencies[current_dependencies.length - 1]) { current_dependencies.push(signal); } + if ( + current_untracked_writes !== null && + current_effect !== null && + (current_effect.f & CLEAN) !== 0 && + current_untracked_writes.includes(signal) + ) { + set_signal_status(current_effect, DIRTY); + schedule_effect(current_effect, false); + } } if ((flags & DERIVED) !== 0 && is_signal_dirty(signal)) { - update_derived(/** @type {import('./types.js').ComputationSignal<V>} **/ (signal), false); + if (DEV) { + // we want to avoid tracking indirect dependencies + const previous_inspect_fn = inspect_fn; + inspect_fn = null; + update_derived(/** @type {import('./types.js').ComputationSignal<V>} **/ (signal), false); + inspect_fn = previous_inspect_fn; + } else { + update_derived(/** @type {import('./types.js').ComputationSignal<V>} **/ (signal), false); + } } return signal.v; } @@ -811,7 +903,7 @@ export function set(signal, value) { * @returns {void} */ export function set_sync(signal, value) { - flushSync(() => set_signal_value(signal, value)); + flushSync(() => set(signal, value)); } /** @@ -995,12 +1087,18 @@ export function set_signal_value(signal, value) { is_runes(component_context) && current_effect !== null && current_effect.c === null && - (current_effect.f & CLEAN) !== 0 && - current_dependencies !== null && - current_dependencies.includes(signal) + (current_effect.f & CLEAN) !== 0 ) { - set_signal_status(current_effect, DIRTY); - schedule_effect(current_effect, false); + if (current_dependencies !== null && current_dependencies.includes(signal)) { + set_signal_status(current_effect, DIRTY); + schedule_effect(current_effect, false); + } else { + if (current_untracked_writes === null) { + current_untracked_writes = [signal]; + } else { + current_untracked_writes.push(signal); + } + } } mark_signal_consumers(signal, DIRTY, true); // If we have afterUpdates locally on the component, but we're within a render effect @@ -1016,6 +1114,12 @@ export function set_signal_value(signal, value) { }); } } + + // @ts-expect-error + if (DEV && signal.inspect) { + // @ts-expect-error + for (const fn of signal.inspect) fn(); + } } } @@ -1056,11 +1160,10 @@ export function destroy_signal(signal) { /** * @template V * @param {() => V} init - * @param {import('./types.js').EqualsFunctions} [equals] * @returns {import('./types.js').ComputationSignal<V>} */ /*#__NO_SIDE_EFFECTS__*/ -export function derived(init, equals) { +export function derived(init) { const is_unowned = current_effect === null; const flags = is_unowned ? DERIVED | UNOWNED : DERIVED; const signal = /** @type {import('./types.js').ComputationSignal<V>} */ ( @@ -1068,7 +1171,7 @@ export function derived(init, equals) { ); signal.i = init; signal.x = current_component_context; - signal.e = get_equals_method(equals); + signal.e = default_equals; if (!is_unowned) { push_reference(/** @type {import('./types.js').EffectSignal} */ (current_effect), signal); } @@ -1078,30 +1181,25 @@ export function derived(init, equals) { /** * @template V * @param {V} initial_value - * @param {import('./types.js').EqualsFunctions<V>} [equals] * @returns {import('./types.js').SourceSignal<V>} */ /*#__NO_SIDE_EFFECTS__*/ -export function source(initial_value, equals) { +export function source(initial_value) { const source = create_source_signal(SOURCE | CLEAN, initial_value); source.x = current_component_context; - source.e = get_equals_method(equals); return source; } /** - * @param {import('./types.js').EqualsFunctions} [equals] - * @returns {import('./types.js').EqualsFunctions} + * @template V + * @param {V} initial_value + * @returns {import('./types.js').SourceSignal<V>} */ -function get_equals_method(equals) { - if (equals !== undefined) { - return equals; - } - const context = current_component_context; - if (context && !context.i) { - return safe_equal; - } - return default_equals; +/*#__NO_SIDE_EFFECTS__*/ +export function mutable_source(initial_value) { + const s = source(initial_value); + s.e = safe_equal; + return s; } /** @@ -1143,6 +1241,13 @@ function internal_create_effect(type, init, sync, block, schedule) { return signal; } +/** + * @returns {boolean} + */ +export function effect_active() { + return current_effect ? (current_effect.f & MANAGED) === 0 : false; +} + /** * @param {() => void | (() => void)} init * @returns {import('./types.js').EffectSignal} @@ -1177,6 +1282,17 @@ export function user_effect(init) { return effect; } +/** + * @param {() => void | (() => void)} init + * @returns {() => void} + */ +export function user_root_effect(init) { + const effect = managed_render_effect(init); + return () => { + destroy_signal(effect); + }; +} + /** * @param {() => void | (() => void)} init * @returns {import('./types.js').EffectSignal} @@ -1306,6 +1422,20 @@ export function is_signal(val) { ); } +/** + * @template O + * @template P + * @param {any} val + * @returns {val is import('./types.js').LazyProperty<O, P>} + */ +export function is_lazy_property(val) { + return ( + typeof val === 'object' && + val !== null && + /** @type {import('./types.js').LazyProperty<O, P>} */ (val).t === LAZY_PROPERTY + ); +} + /** * @template V * @param {unknown} val @@ -1333,11 +1463,12 @@ export function is_store(val) { * @template V * @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props_obj * @param {string} key + * @param {boolean} immutable * @param {V | (() => V)} [default_value] * @param {boolean} [call_default_value] * @returns {import('./types.js').Signal<V> | (() => V)} */ -export function prop_source(props_obj, key, default_value, call_default_value) { +export function prop_source(props_obj, key, immutable, default_value, call_default_value) { const props = is_signal(props_obj) ? get(props_obj) : props_obj; const possible_signal = /** @type {import('./types.js').MaybeSignal<V>} */ ( expose(() => props[key]) @@ -1349,8 +1480,7 @@ export function prop_source(props_obj, key, default_value, call_default_value) { if ( is_signal(possible_signal) && possible_signal.v === value && - update_bound_prop === undefined && - get_equals_method() === possible_signal.e + update_bound_prop === undefined ) { if (should_set_default_value) { set( @@ -1368,15 +1498,14 @@ export function prop_source(props_obj, key, default_value, call_default_value) { call_default_value ? default_value() : default_value; } - const source_signal = source(value); + const source_signal = immutable ? source(value) : mutable_source(value); // Synchronize prop changes with source signal. // Needs special equality checking because the prop in the // parent could be changed through `foo.bar = 'new value'`. - const immutable = /** @type {import('./types.js').ComponentContext} */ (current_component_context) - .i; let ignore_next1 = false; let ignore_next2 = false; + let did_update_to_defined = !should_set_default_value; let mount = true; sync_effect(() => { @@ -1392,8 +1521,13 @@ export function prop_source(props_obj, key, default_value, call_default_value) { return; } - if (not_equal(immutable, propagating_value, source_signal.v)) { + if ( + // Ensure that updates from undefined to undefined are ignored + (did_update_to_defined || propagating_value !== undefined) && + not_equal(immutable, propagating_value, source_signal.v) + ) { ignore_next2 = true; + did_update_to_defined = true; // TODO figure out why we need it this way and the explain in a comment; // some tests fail is we just do set_signal_value(source_signal, propagating_value) untrack(() => set_signal_value(source_signal, propagating_value)); @@ -1416,6 +1550,7 @@ export function prop_source(props_obj, key, default_value, call_default_value) { if (not_equal(immutable, propagating_value, possible_signal.v)) { ignore_next1 = true; + did_update_to_defined = true; untrack(() => update_bound_prop(propagating_value)); } }); @@ -1426,16 +1561,12 @@ export function prop_source(props_obj, key, default_value, call_default_value) { /** * If the prop is readonly and has no fallback value, we can use this function, else we need to use `prop_source`. - * @template V * @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props_obj * @param {string} key * @returns {any} */ export function prop(props_obj, key) { - return () => { - const props = is_signal(props_obj) ? get(props_obj) : props_obj; - return /** @type {V} */ (props[key]); - }; + return is_signal(props_obj) ? () => get(props_obj)[key] : () => props_obj[key]; } /** @@ -1523,7 +1654,7 @@ export function bubble_event($$props, event) { const events = /** @type {Record<string, Function[] | Function>} */ (unwrap($$props).$$events)?.[ event.type ]; - const callbacks = is_array(events) ? events.slice() : [events]; + const callbacks = is_array(events) ? events.slice() : events == null ? [] : [events]; let fn; for (fn of callbacks) { // Preserve "this" context @@ -1678,13 +1809,11 @@ export function onDestroy(fn) { /** * @param {import('./types.js').MaybeSignal<Record<string, unknown>>} props * @param {any} runes - * @param {any} immutable * @returns {void} */ -export function push(props, runes = false, immutable = false) { +export function push(props, runes = false) { const context_stack_item = create_component_context(props); context_stack_item.r = runes; - context_stack_item.i = immutable; current_component_context = context_stack_item; } @@ -1709,3 +1838,101 @@ export function pop(accessors) { context_stack_item.m = true; } } + +/** + * @param {any} value + * @param {Set<any>} visited + * @returns {void} + */ +function deep_read(value, visited = new Set()) { + if (typeof value === 'object' && value !== null && !visited.has(value)) { + visited.add(value); + for (let key in value) { + deep_read(value[key], visited); + } + const proto = Object.getPrototypeOf(value); + if ( + proto !== Object.prototype && + proto !== Array.prototype && + proto !== Map.prototype && + proto !== Set.prototype && + proto !== Date.prototype + ) { + const descriptors = get_descriptors(proto); + for (let key in descriptors) { + const get = descriptors[key].get; + if (get) { + get.call(value); + } + } + } + } +} + +/** + * @param {() => import('./types.js').MaybeSignal<>} get_value + * @param {Function} inspect + * @returns {void} + */ +// eslint-disable-next-line no-console +export function inspect(get_value, inspect = console.log) { + let initial = true; + + pre_effect(() => { + const fn = () => { + const value = get_value(); + inspect(value, initial ? 'init' : 'update'); + }; + + inspect_fn = fn; + const value = get_value(); + deep_read(value); + inspect_fn = null; + + const signals = inspect_captured_signals.slice(); + inspect_captured_signals = []; + + if (initial) { + fn(); + initial = false; + } + + return () => { + for (const s of signals) { + s.inspect.delete(fn); + } + }; + }); +} + +/** + * @template O + * @template P + * @param {O} o + * @param {P} p + * @returns {import('./types.js').LazyProperty<O, P>} + */ +export function lazy_property(o, p) { + return { + o, + p, + t: LAZY_PROPERTY + }; +} + +/** + * @template V + * @param {V} value + * @returns {import('./types.js').UnwrappedSignal<V>} + */ +export function unwrap(value) { + if (is_signal(value)) { + // @ts-ignore + return get(value); + } + if (is_lazy_property(value)) { + return value.o[value.p]; + } + // @ts-ignore + return value; +} diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 8b2666a71..d55dbbba5 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -9,8 +9,9 @@ import { KEY_BLOCK, ROOT_BLOCK } from './block.js'; +import { destroy_each_item_block } from './each.js'; import { append_child } from './operations.js'; -import { destroy_each_item_block, empty } from './render.js'; +import { empty } from './render.js'; import { current_block, current_effect, @@ -176,9 +177,12 @@ class TickAnimation { } cancel() { - const t = this.#reversed ? 1 : 0; active_tick_animations.delete(this); - this.#tick_fn(t, 1 - t); + const current = this.#current / this.#duration; + if (current > 0 && current < 1) { + const t = this.#reversed ? 1 : 0; + this.#tick_fn(t, 1 - t); + } } finish() { @@ -321,7 +325,7 @@ function create_transition(dom, init, direction, effect) { animation.onfinish = () => { const is_outro = curr_direction === 'out'; - /** @type {Animation | TickAnimation} */ (animation).pause(); + /** @type {Animation | TickAnimation} */ (animation).cancel(); if (is_outro) { run_all(subs); subs = []; diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 1b41f6e0a..13bfbc16c 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -10,7 +10,7 @@ import { DYNAMIC_ELEMENT_BLOCK, SNIPPET_BLOCK } from './block.js'; -import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT } from './runtime.js'; +import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT, LAZY_PROPERTY } from './runtime.js'; // Put all internal types in this file. Once we convert to JSDoc, we can make this a d.ts file @@ -47,8 +47,6 @@ export type ComponentContext = { p: null | ComponentContext; /** context */ c: null | Map<unknown, unknown>; - /** immutable */ - i: boolean; /** runes */ r: boolean; /** update_callbacks */ @@ -80,6 +78,11 @@ export type SourceSignal<V = unknown> = { v: V; }; +export type SourceSignalDebug = { + /** This is DEV only */ + inspect: Set<Function>; +}; + export type ComputationSignal<V = unknown> = { /** block: The block associated with this effect/computed */ b: null | Block; @@ -111,6 +114,12 @@ export type MaybeSignal<T = unknown> = T | Signal<T>; export type UnwrappedSignal<T> = T extends Signal<infer U> ? U : T; +export type LazyProperty<O, P> = { + o: O; + p: P; + t: typeof LAZY_PROPERTY; +}; + export type EqualsFunctions<T = any> = (a: T, v: T) => boolean; export type BlockType = diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index 831ccd901..084d51d8f 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -1,3 +1,4 @@ +import { untrack } from './runtime.js'; import { is_array } from './utils.js'; /** regex of all html void element names */ @@ -69,7 +70,7 @@ export function validate_dynamic_element_tag(tag_fn) { */ export function validate_each_keys(collection, key_fn) { const keys = new Map(); - const maybe_array = collection(); + const maybe_array = untrack(() => collection()); const array = is_array(maybe_array) ? maybe_array : maybe_array == null diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index a0796cd37..840e8cfd4 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -7,6 +7,7 @@ export { expose, exposable, source, + mutable_source, derived, prop, prop_source, @@ -35,12 +36,18 @@ export { onDestroy, pop, push, - reactive_import + reactive_import, + effect_active, + user_root_effect, + inspect, + unwrap } from './client/runtime.js'; -export * from './client/validate.js'; - +export * from './client/each.js'; export * from './client/render.js'; +export * from './client/validate.js'; +export { raf } from './client/timing.js'; +export { proxy, readonly } from './client/proxy/proxy.js'; export { create_custom_element } from './client/custom-element.js'; @@ -51,5 +58,3 @@ export { $window as window, $document as document } from './client/operations.js'; - -export { raf } from './client/timing.js'; diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index 7d5773752..562ee2270 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -2,6 +2,7 @@ import * as $ from '../client/runtime.js'; import { set_is_ssr } from '../client/runtime.js'; import { is_promise } from '../common.js'; import { subscribe_to_store } from '../../store/utils.js'; +import { DOMBooleanAttributes } from '../../constants.js'; export * from '../client/validate.js'; @@ -31,34 +32,6 @@ const CONTENT_REGEX = /[&<]/g; const INVALID_ATTR_NAME_CHAR_REGEX = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u; -// This is duplicated from the compiler, but we need it at runtime too. -export const DOMBooleanAttributes = [ - 'allowfullscreen', - 'async', - 'autofocus', - 'autoplay', - 'checked', - 'controls', - 'default', - 'disabled', - 'formnovalidate', - 'hidden', - 'indeterminate', - 'ismap', - 'loop', - 'multiple', - 'muted', - 'nomodule', - 'novalidate', - 'open', - 'playsinline', - 'readonly', - 'required', - 'reversed', - 'seamless', - 'selected' -]; - export const VoidElements = new Set([ 'area', 'base', @@ -142,10 +115,9 @@ export function render(component, options) { /** * @param {boolean} runes - * @param {boolean} [immutable] */ -export function push(runes, immutable) { - $.push({}, runes, immutable); +export function push(runes) { + $.push({}, runes); } export function pop() { @@ -177,6 +149,17 @@ export function escape(value, is_attr = false) { return escaped + str.substring(last); } +/** + * @template V + * @param {V} value + * @returns {string} + */ +export function escape_text(value) { + const escaped = escape(value); + // If the value is empty, then ensure we put a space so that it creates a text node on the client + return escaped === '' ? ' ' : escaped; +} + /** * @param {Payload} payload * @param {(head_payload: Payload['head']) => void} fn @@ -225,11 +208,13 @@ export function css_props(payload, is_html, props, component) { /** * @param {Record<string, unknown>[]} attrs + * @param {boolean} lowercase_attributes + * @param {boolean} is_svg * @param {string} class_hash * @param {{ styles: Record<string, string> | null; classes: string }} [additional] * @returns {string} */ -export function spread_attributes(attrs, class_hash, additional) { +export function spread_attributes(attrs, lowercase_attributes, is_svg, class_hash, additional) { /** @type {Record<string, unknown>} */ const merged_attrs = {}; let key; @@ -276,7 +261,10 @@ export function spread_attributes(attrs, class_hash, additional) { for (name in merged_attrs) { if (INVALID_ATTR_NAME_CHAR_REGEX.test(name)) continue; - const is_boolean = DOMBooleanAttributes.includes(name); + if (lowercase_attributes) { + name = name.toLowerCase(); + } + const is_boolean = !is_svg && DOMBooleanAttributes.includes(name); attr_str += attr(name, merged_attrs[name], is_boolean); } diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js index 8c6321320..9ad99401d 100644 --- a/packages/svelte/src/legacy/legacy-client.js +++ b/packages/svelte/src/legacy/legacy-client.js @@ -68,7 +68,6 @@ class Svelte4Component { target: options.target, props: { ...options.props, $$events: this.#events }, context: options.context, - immutable: options.immutable, intro: options.intro, recover: options.recover }); diff --git a/packages/svelte/src/main/ambient.d.ts b/packages/svelte/src/main/ambient.d.ts index d80e8e432..a351e48b3 100644 --- a/packages/svelte/src/main/ambient.d.ts +++ b/packages/svelte/src/main/ambient.d.ts @@ -68,6 +68,56 @@ declare namespace $effect { * @param fn The function to execute */ export function pre(fn: () => void | (() => void)): void; + + /** + * The `$effect.active` rune is an advanced feature that tells you whether or not the code is running inside an effect or inside your template. + * + * Example: + * ```svelte + * <script> + * console.log('in component setup:', $effect.active()); // false + * + * $effect(() => { + * console.log('in effect:', $effect.active()); // true + * }); + * </script> + * + * <p>in template: {$effect.active()}</p> <!-- true --> + * ``` + * + * This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects. + * + * https://svelte-5-preview.vercel.app/docs/runes#$effect-active + */ + export function active(): boolean; + + /** + * The `$effect.root` rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for + * nested effects that you want to manually control. This rune also allows for creation of effects outside of the component + * initialisation phase. + * + * Example: + * ```svelte + * <script> + * let count = $state(0); + * + * const cleanup = $effect.root(() => { + * $effect(() => { + * console.log(count); + * }) + * + * return () => { + * console.log('effect root cleanup'); + * } + * }); + * </script> + * + * <button onclick={() => cleanup()}>cleanup</button> + * ``` + * + * https://svelte-5-preview.vercel.app/docs/runes#$effect-root + */ + export function root(fn: () => void | (() => void)): () => void; } /** @@ -80,3 +130,25 @@ declare namespace $effect { * https://svelte-5-preview.vercel.app/docs/runes#$props */ declare function $props<T>(): T; + +/** + * Inspects a value whenever it, or the properties it contains, change. Example: + * + * ```ts + * $inspect({ someValue, someOtherValue }) + * ``` + * + * If a second argument is provided, it will be called with the value and the event type + * (`'init'` or `'update'`), otherwise the value will be logged to the console. + * + * ```ts + * $inspect(x, console.trace); + * $inspect(y, (y) => { debugger; }); + * ``` + * + * https://svelte-5-preview.vercel.app/docs/runes#$inspect + */ +declare function $inspect<T>( + value: T, + callback?: (value: T, type: 'init' | 'update') => void +): void; diff --git a/packages/svelte/src/main/public.d.ts b/packages/svelte/src/main/public.d.ts index e98a62734..5849835ea 100644 --- a/packages/svelte/src/main/public.d.ts +++ b/packages/svelte/src/main/public.d.ts @@ -18,6 +18,14 @@ export interface ComponentConstructorOptions< $$inline?: boolean; } +// Utility type for ensuring backwards compatibility on a type level: If there's a default slot, add 'children' to the props if it doesn't exist there already +type PropsWithChildren<Props, Slots> = Props & + (Props extends { children?: any } + ? {} + : Slots extends { default: any } + ? { children?: Snippet } + : {}); + /** * Can be used to create strongly typed Svelte components. * @@ -52,25 +60,18 @@ export class SvelteComponent< Slots extends Record<string, any> = any > { [prop: string]: any; - - /** - * For type checking capabilities only. - * Does not exist at runtime. - * ### DO NOT USE! - */ - constructor(props: Props); /** * @deprecated This constructor only exists when using the `asClassComponent` compatibility helper, which * is a stop-gap solution. Migrate towards using `mount` or `createRoot` instead. See * https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more info. */ - constructor(options: ComponentConstructorOptions<Props>); + constructor(options: ComponentConstructorOptions<PropsWithChildren<Props, Slots>>); /** * For type checking capabilities only. * Does not exist at runtime. * ### DO NOT USE! * */ - $$prop_def: Props; + $$prop_def: PropsWithChildren<Props, Slots>; /** * For type checking capabilities only. * Does not exist at runtime. @@ -195,7 +196,9 @@ declare const SnippetReturn: unique symbol; * You can only call a snippet through the `{@render ...}` tag. */ export interface Snippet<T = void> { - (arg: T): typeof SnippetReturn; + (arg: T): typeof SnippetReturn & { + _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; + }; } interface DispatchOptions { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index a081a8b89..d59ed5b1e 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.9'; +export const VERSION = '5.0.0-next.18'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/animation-helpers.js b/packages/svelte/tests/animation-helpers.js index 8855d813b..21635a05b 100644 --- a/packages/svelte/tests/animation-helpers.js +++ b/packages/svelte/tests/animation-helpers.js @@ -102,16 +102,18 @@ class Animation { } finish() { + this.onfinish(); this.currentTime = this.#reversed ? 0 : this.#duration; if (this.#reversed) { raf.animations.delete(this); } - this.onfinish(); } cancel() { - this._applyKeyFrame(this.#reversed ? this.#keyframes.length - 1 : 0); - raf.animations.delete(this); + this.#paused = true; + if (this.currentTime > 0 && this.currentTime < this.#duration) { + this._applyKeyFrame(this.#reversed ? this.#keyframes.length - 1 : 0); + } } pause() { diff --git a/packages/svelte/tests/compiler-errors/samples/attribute-empty/_config.js b/packages/svelte/tests/compiler-errors/samples/attribute-empty/_config.js index 5823dca91..248fc1107 100644 --- a/packages/svelte/tests/compiler-errors/samples/attribute-empty/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/attribute-empty/_config.js @@ -3,7 +3,7 @@ import { test } from '../../test'; export default test({ error: { code: 'missing-attribute-value', - message: 'Expected value for the attribute', + message: 'Expected attribute value', position: [12, 12] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/attribute-empty/input.svelte b/packages/svelte/tests/compiler-errors/samples/attribute-empty/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/attribute-empty/input.svelte rename to packages/svelte/tests/compiler-errors/samples/attribute-empty/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/attribute-unique-binding/input.svelte b/packages/svelte/tests/compiler-errors/samples/attribute-unique-binding/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/attribute-unique-binding/input.svelte rename to packages/svelte/tests/compiler-errors/samples/attribute-unique-binding/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/attribute-unique-shorthand/input.svelte b/packages/svelte/tests/compiler-errors/samples/attribute-unique-shorthand/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/attribute-unique-shorthand/input.svelte rename to packages/svelte/tests/compiler-errors/samples/attribute-unique-shorthand/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/attribute-unique/input.svelte b/packages/svelte/tests/compiler-errors/samples/attribute-unique/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/attribute-unique/input.svelte rename to packages/svelte/tests/compiler-errors/samples/attribute-unique/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/catch-before-closing/_config.js b/packages/svelte/tests/compiler-errors/samples/catch-before-closing/_config.js index 5fb8c58c2..1a50076da 100644 --- a/packages/svelte/tests/compiler-errors/samples/catch-before-closing/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/catch-before-closing/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-catch-placement', - message: 'Expected to close {#each} block before seeing {:catch} block', - position: [41, 41] + code: 'expected-token', + message: 'Expected token {:else}', + position: [35, 35] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/catch-before-closing/input.svelte b/packages/svelte/tests/compiler-errors/samples/catch-before-closing/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/catch-before-closing/input.svelte rename to packages/svelte/tests/compiler-errors/samples/catch-before-closing/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/catch-without-await/_config.js b/packages/svelte/tests/compiler-errors/samples/catch-without-await/_config.js index 63f3b5e3c..64c85f85c 100644 --- a/packages/svelte/tests/compiler-errors/samples/catch-without-await/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/catch-without-await/_config.js @@ -2,8 +2,9 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-catch-placement', - message: 'Cannot have an {:catch} block outside an {#await ...} block', - position: [7, 7] + code: 'invalid-continuing-block-placement', + message: + '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)', + position: [1, 1] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/catch-without-await/input.svelte b/packages/svelte/tests/compiler-errors/samples/catch-without-await/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/catch-without-await/input.svelte rename to packages/svelte/tests/compiler-errors/samples/catch-without-await/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/class-state-field-static/_config.js b/packages/svelte/tests/compiler-errors/samples/class-state-field-static/_config.js index b6dca4e40..27991ba42 100644 --- a/packages/svelte/tests/compiler-errors/samples/class-state-field-static/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/class-state-field-static/_config.js @@ -4,6 +4,6 @@ export default test({ error: { code: 'invalid-state-location', message: '$state() can only be used as a variable declaration initializer or a class field', - position: process.platform === 'win32' ? [35, 43] : [33, 41] + position: [33, 41] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/comment-unclosed/_config.js b/packages/svelte/tests/compiler-errors/samples/comment-unclosed/_config.js index 166a9df9b..7c00375ad 100644 --- a/packages/svelte/tests/compiler-errors/samples/comment-unclosed/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/comment-unclosed/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'unclosed-comment', - message: 'comment was left open, expected -->', + code: 'unexpected-eof', + message: 'Unexpected end of input (expected -->)', position: [24, 24] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/comment-unclosed/input.svelte b/packages/svelte/tests/compiler-errors/samples/comment-unclosed/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/comment-unclosed/input.svelte rename to packages/svelte/tests/compiler-errors/samples/comment-unclosed/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/css-global-without-selector/_config.js b/packages/svelte/tests/compiler-errors/samples/css-global-without-selector/_config.js index c8e598d4a..549ad3ebe 100644 --- a/packages/svelte/tests/compiler-errors/samples/css-global-without-selector/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/css-global-without-selector/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'css-syntax-error', - message: ':global() must contain a selector', - position: [9, 9] + code: 'invalid-css-global-selector', + message: ':global(...) must contain exactly one selector', + position: [16, 16] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/css-global-without-selector/input.svelte b/packages/svelte/tests/compiler-errors/samples/css-global-without-selector/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/css-global-without-selector/input.svelte rename to packages/svelte/tests/compiler-errors/samples/css-global-without-selector/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/css/_config.js b/packages/svelte/tests/compiler-errors/samples/css/_config.js index d1a7e346f..9705cde6b 100644 --- a/packages/svelte/tests/compiler-errors/samples/css/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/css/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'css-syntax-error', - message: '"{" is expected', - position: [24, 24] + code: 'invalid-css-identifier', + message: 'Expected a valid CSS identifier', + position: [25, 25] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/css/input.svelte b/packages/svelte/tests/compiler-errors/samples/css/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/css/input.svelte rename to packages/svelte/tests/compiler-errors/samples/css/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/dollar-binding-global-js/main.js b/packages/svelte/tests/compiler-errors/samples/dollar-binding-global-js/main.svelte.js similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/dollar-binding-global-js/main.js rename to packages/svelte/tests/compiler-errors/samples/dollar-binding-global-js/main.svelte.js diff --git a/packages/svelte/tests/compiler-errors/samples/else-before-closing-2/_config.js b/packages/svelte/tests/compiler-errors/samples/else-before-closing-2/_config.js index 7b1d43bf1..dc9aff145 100644 --- a/packages/svelte/tests/compiler-errors/samples/else-before-closing-2/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/else-before-closing-2/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-else-placement', - message: 'Expected to close {#await} block before seeing {:else} block', - position: [29, 29] + code: 'expected-token', + message: 'Expected token {:then ...} or {:catch ...}', + position: [24, 24] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/else-before-closing-2/input.svelte b/packages/svelte/tests/compiler-errors/samples/else-before-closing-2/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/else-before-closing-2/input.svelte rename to packages/svelte/tests/compiler-errors/samples/else-before-closing-2/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/else-before-closing-3/_config.js b/packages/svelte/tests/compiler-errors/samples/else-before-closing-3/_config.js index ab7281cee..80a2a136d 100644 --- a/packages/svelte/tests/compiler-errors/samples/else-before-closing-3/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/else-before-closing-3/_config.js @@ -2,8 +2,9 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-else-placement', - message: 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block', - position: [11, 11] + code: 'invalid-continuing-block-placement', + message: + '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)', + position: [6, 6] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/else-before-closing-3/input.svelte b/packages/svelte/tests/compiler-errors/samples/else-before-closing-3/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/else-before-closing-3/input.svelte rename to packages/svelte/tests/compiler-errors/samples/else-before-closing-3/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/else-before-closing/_config.js b/packages/svelte/tests/compiler-errors/samples/else-before-closing/_config.js index 31635f212..87c1296f4 100644 --- a/packages/svelte/tests/compiler-errors/samples/else-before-closing/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/else-before-closing/_config.js @@ -2,8 +2,9 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-else-placement', - message: 'Expected to close <li> tag before seeing {:else} block', - position: [23, 23] + code: 'invalid-continuing-block-placement', + message: + '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)', + position: [18, 18] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/else-before-closing/input.svelte b/packages/svelte/tests/compiler-errors/samples/else-before-closing/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/else-before-closing/input.svelte rename to packages/svelte/tests/compiler-errors/samples/else-before-closing/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/else-if-before-closing-2/_config.js b/packages/svelte/tests/compiler-errors/samples/else-if-before-closing-2/_config.js index 10ed037ed..4b4059cf3 100644 --- a/packages/svelte/tests/compiler-errors/samples/else-if-before-closing-2/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/else-if-before-closing-2/_config.js @@ -2,8 +2,9 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-elseif-placement', - message: 'Expected to close <p> tag before seeing {:else if ...} block', - position: [25, 25] + code: 'invalid-continuing-block-placement', + message: + '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)', + position: [17, 17] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/else-if-before-closing-2/input.svelte b/packages/svelte/tests/compiler-errors/samples/else-if-before-closing-2/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/else-if-before-closing-2/input.svelte rename to packages/svelte/tests/compiler-errors/samples/else-if-before-closing-2/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/else-if-before-closing/_config.js b/packages/svelte/tests/compiler-errors/samples/else-if-before-closing/_config.js index 70823c995..ae20d2da8 100644 --- a/packages/svelte/tests/compiler-errors/samples/else-if-before-closing/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/else-if-before-closing/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-elseif-placement', - message: 'Expected to close {#await} block before seeing {:else if ...} block', - position: [34, 34] + code: 'expected-token', + message: 'Expected token {:then ...} or {:catch ...}', + position: [26, 26] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/else-if-before-closing/input.svelte b/packages/svelte/tests/compiler-errors/samples/else-if-before-closing/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/else-if-before-closing/input.svelte rename to packages/svelte/tests/compiler-errors/samples/else-if-before-closing/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/else-if-without-if/_config.js b/packages/svelte/tests/compiler-errors/samples/else-if-without-if/_config.js index cb8e35c41..04d8342f2 100644 --- a/packages/svelte/tests/compiler-errors/samples/else-if-without-if/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/else-if-without-if/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-elseif-placement', - message: 'Cannot have an {:else if ...} block outside an {#if ...} block', - position: [35, 35] + code: 'expected-token', + message: 'Expected token {:then ...} or {:catch ...}', + position: [27, 27] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/else-if-without-if/input.svelte b/packages/svelte/tests/compiler-errors/samples/else-if-without-if/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/else-if-without-if/input.svelte rename to packages/svelte/tests/compiler-errors/samples/else-if-without-if/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/empty-attribute-shorthand/input.svelte b/packages/svelte/tests/compiler-errors/samples/empty-attribute-shorthand/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/empty-attribute-shorthand/input.svelte rename to packages/svelte/tests/compiler-errors/samples/empty-attribute-shorthand/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/empty-classname-binding/_config.js b/packages/svelte/tests/compiler-errors/samples/empty-classname-binding/_config.js index 0051e827a..bd1d1d768 100644 --- a/packages/svelte/tests/compiler-errors/samples/empty-classname-binding/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/empty-classname-binding/_config.js @@ -3,7 +3,7 @@ import { test } from '../../test'; export default test({ error: { code: 'empty-directive-name', - message: 'Class name cannot be empty', + message: 'ClassDirective name cannot be empty', position: [10, 10] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/empty-classname-binding/input.svelte b/packages/svelte/tests/compiler-errors/samples/empty-classname-binding/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/empty-classname-binding/input.svelte rename to packages/svelte/tests/compiler-errors/samples/empty-classname-binding/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/empty-directive-name/_config.js b/packages/svelte/tests/compiler-errors/samples/empty-directive-name/_config.js index 7f6d0f1d0..48a0eb32c 100644 --- a/packages/svelte/tests/compiler-errors/samples/empty-directive-name/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/empty-directive-name/_config.js @@ -3,7 +3,7 @@ import { test } from '../../test'; export default test({ error: { code: 'empty-directive-name', - message: 'Action name cannot be empty', + message: 'UseDirective name cannot be empty', position: [8, 8] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/empty-directive-name/input.svelte b/packages/svelte/tests/compiler-errors/samples/empty-directive-name/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/empty-directive-name/input.svelte rename to packages/svelte/tests/compiler-errors/samples/empty-directive-name/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js b/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js new file mode 100644 index 000000000..1c171d19c --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js @@ -0,0 +1,9 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'invalid-derived-export', + message: 'Cannot export derived state', + position: [24, 66] + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte.js new file mode 100644 index 000000000..0906cedf7 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte.js @@ -0,0 +1,3 @@ +let count = $state(0); + +export const double = $derived(count * 2); diff --git a/packages/svelte/tests/compiler-errors/samples/export-state/_config.js b/packages/svelte/tests/compiler-errors/samples/export-state/_config.js new file mode 100644 index 000000000..5ddb2a859 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-state/_config.js @@ -0,0 +1,9 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'invalid-state-export', + message: 'Cannot export state if it is reassigned', + position: [46, 86] + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/export-state/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/export-state/main.svelte.js new file mode 100644 index 000000000..39d9920f7 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-state/main.svelte.js @@ -0,0 +1,13 @@ +export const object = $state({ + ok: true +}); + +export const primitive = $state('nope'); + +export function update_object() { + object.ok = !object.ok; +} + +export function update_primitive() { + primitive = 'yep'; +} diff --git a/packages/svelte/tests/compiler-errors/samples/illegal-expression/_config.js b/packages/svelte/tests/compiler-errors/samples/illegal-expression/_config.js index 659679c34..887f11e88 100644 --- a/packages/svelte/tests/compiler-errors/samples/illegal-expression/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/illegal-expression/_config.js @@ -2,7 +2,7 @@ import { test } from '../../test'; export default test({ error: { - code: 'parse-error', + code: 'js-parse-error', message: 'Assigning to rvalue', position: [1, 1] } diff --git a/packages/svelte/tests/compiler-errors/samples/illegal-expression/input.svelte b/packages/svelte/tests/compiler-errors/samples/illegal-expression/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/illegal-expression/input.svelte rename to packages/svelte/tests/compiler-errors/samples/illegal-expression/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/multiple-styles/_config.js b/packages/svelte/tests/compiler-errors/samples/multiple-styles/_config.js index da3e69fc1..50cf2583a 100644 --- a/packages/svelte/tests/compiler-errors/samples/multiple-styles/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/multiple-styles/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'duplicate-style', - message: 'You can only have one top-level <style> tag per component', + code: 'duplicate-style-element', + message: 'A component can have a single top-level <style> element', position: [58, 58] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/multiple-styles/input.svelte b/packages/svelte/tests/compiler-errors/samples/multiple-styles/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/multiple-styles/input.svelte rename to packages/svelte/tests/compiler-errors/samples/multiple-styles/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/raw-mustaches-whitespace/input.svelte b/packages/svelte/tests/compiler-errors/samples/raw-mustaches-whitespace/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/raw-mustaches-whitespace/input.svelte rename to packages/svelte/tests/compiler-errors/samples/raw-mustaches-whitespace/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/_config.js index effac3060..a42a65114 100644 --- a/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/_config.js @@ -2,7 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: '', - message: 'Cannot export value created with $state' + code: 'invalid-state-export', + message: 'Cannot export state if it is reassigned', + position: [28, 53] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/main.js b/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/main.js deleted file mode 100644 index 714838f27..000000000 --- a/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/main.js +++ /dev/null @@ -1 +0,0 @@ -export const x = $state(0); diff --git a/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/main.svelte.js new file mode 100644 index 000000000..af62861e6 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/runes-export-named-state/main.svelte.js @@ -0,0 +1,3 @@ +export const x = $state(0); +export let y = $state(0); +y = 1; diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-const-assignment/main.js b/packages/svelte/tests/compiler-errors/samples/runes-no-const-assignment/main.svelte.js similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/runes-no-const-assignment/main.js rename to packages/svelte/tests/compiler-errors/samples/runes-no-const-assignment/main.svelte.js diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-derived-args/main.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-derived-args/main.svelte.js similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/runes-wrong-derived-args/main.js rename to packages/svelte/tests/compiler-errors/samples/runes-wrong-derived-args/main.svelte.js diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-derived-placement/main.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-derived-placement/main.svelte.js similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/runes-wrong-derived-placement/main.js rename to packages/svelte/tests/compiler-errors/samples/runes-wrong-derived-placement/main.svelte.js diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-effect-args/main.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-effect-args/main.svelte.js similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/runes-wrong-effect-args/main.js rename to packages/svelte/tests/compiler-errors/samples/runes-wrong-effect-args/main.svelte.js diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-effect-placement/main.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-effect-placement/main.svelte.js similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/runes-wrong-effect-placement/main.js rename to packages/svelte/tests/compiler-errors/samples/runes-wrong-effect-placement/main.svelte.js diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-props-placement/main.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-props-placement/main.svelte.js similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/runes-wrong-props-placement/main.js rename to packages/svelte/tests/compiler-errors/samples/runes-wrong-props-placement/main.svelte.js diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-args/main.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-args/main.svelte.js similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/runes-wrong-state-args/main.js rename to packages/svelte/tests/compiler-errors/samples/runes-wrong-state-args/main.svelte.js diff --git a/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-placement/main.js b/packages/svelte/tests/compiler-errors/samples/runes-wrong-state-placement/main.svelte.js similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/runes-wrong-state-placement/main.js rename to packages/svelte/tests/compiler-errors/samples/runes-wrong-state-placement/main.svelte.js diff --git a/packages/svelte/tests/compiler-errors/samples/script-unclosed-eof/_config.js b/packages/svelte/tests/compiler-errors/samples/script-unclosed-eof/_config.js index 0e41735d0..c8b496e41 100644 --- a/packages/svelte/tests/compiler-errors/samples/script-unclosed-eof/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/script-unclosed-eof/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'unclosed-script', - message: '<script> must have a closing tag', + code: 'unexpected-eof', + message: 'Unexpected end of input', position: [32, 32] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/script-unclosed/input.svelte b/packages/svelte/tests/compiler-errors/samples/script-unclosed-eof/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/script-unclosed/input.svelte rename to packages/svelte/tests/compiler-errors/samples/script-unclosed-eof/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/script-unclosed/_config.js b/packages/svelte/tests/compiler-errors/samples/script-unclosed/_config.js index 0e41735d0..30c59d6f2 100644 --- a/packages/svelte/tests/compiler-errors/samples/script-unclosed/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/script-unclosed/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'unclosed-script', - message: '<script> must have a closing tag', + code: 'unclosed-element', + message: '<script> was left open', position: [32, 32] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/script-unclosed-eof/input.svelte b/packages/svelte/tests/compiler-errors/samples/script-unclosed/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/script-unclosed-eof/input.svelte rename to packages/svelte/tests/compiler-errors/samples/script-unclosed/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/self-reference/_config.js b/packages/svelte/tests/compiler-errors/samples/self-reference/_config.js index 5f5980b7e..8148e1833 100644 --- a/packages/svelte/tests/compiler-errors/samples/self-reference/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/self-reference/_config.js @@ -4,7 +4,7 @@ export default test({ error: { code: 'invalid-self-placement', message: - '<svelte:self> components can only exist inside {#if} blocks, {#each} blocks,, {#snippet} blocks or slots passed to components', + '<svelte:self> components can only exist inside {#if} blocks, {#each} blocks, {#snippet} blocks or slots passed to components', position: [1, 1] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/self-reference/input.svelte b/packages/svelte/tests/compiler-errors/samples/self-reference/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/self-reference/input.svelte rename to packages/svelte/tests/compiler-errors/samples/self-reference/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/style-unclosed-eof/_config.js b/packages/svelte/tests/compiler-errors/samples/style-unclosed-eof/_config.js index 12b200322..8d27a7297 100644 --- a/packages/svelte/tests/compiler-errors/samples/style-unclosed-eof/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/style-unclosed-eof/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'unclosed-style', - message: '<style> must have a closing tag', + code: 'expected-token', + message: 'Expected token </style', position: [31, 31] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/style-unclosed/input.svelte b/packages/svelte/tests/compiler-errors/samples/style-unclosed-eof/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/style-unclosed/input.svelte rename to packages/svelte/tests/compiler-errors/samples/style-unclosed-eof/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/style-unclosed/_config.js b/packages/svelte/tests/compiler-errors/samples/style-unclosed/_config.js index 12b200322..602f19603 100644 --- a/packages/svelte/tests/compiler-errors/samples/style-unclosed/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/style-unclosed/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'unclosed-style', - message: '<style> must have a closing tag', - position: [31, 31] + code: 'invalid-css-identifier', + message: 'Expected a valid CSS identifier', + position: [9, 9] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/style-unclosed-eof/input.svelte b/packages/svelte/tests/compiler-errors/samples/style-unclosed/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/style-unclosed-eof/input.svelte rename to packages/svelte/tests/compiler-errors/samples/style-unclosed/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/svelte-selfdestructive/_config.js b/packages/svelte/tests/compiler-errors/samples/svelte-selfdestructive/_config.js index d102c9f4a..b88609ffe 100644 --- a/packages/svelte/tests/compiler-errors/samples/svelte-selfdestructive/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/svelte-selfdestructive/_config.js @@ -2,9 +2,9 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-tag-name', + code: 'invalid-svelte-tag', message: - 'Valid <svelte:...> tag names are svelte:head, svelte:options, svelte:window, svelte:document, svelte:body, svelte:self, svelte:component, svelte:fragment or svelte:element', + 'Valid <svelte:...> tag names are svelte:head, svelte:options, svelte:window, svelte:document, svelte:body, svelte:element, svelte:component, svelte:self or svelte:fragment', position: [10, 10] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/svelte-selfdestructive/input.svelte b/packages/svelte/tests/compiler-errors/samples/svelte-selfdestructive/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/svelte-selfdestructive/input.svelte rename to packages/svelte/tests/compiler-errors/samples/svelte-selfdestructive/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/then-before-closing/_config.js b/packages/svelte/tests/compiler-errors/samples/then-before-closing/_config.js index cd5dc4213..7c1ea02cc 100644 --- a/packages/svelte/tests/compiler-errors/samples/then-before-closing/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/then-before-closing/_config.js @@ -2,8 +2,9 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-then-placement', - message: 'Expected to close <li> tag before seeing {:then} block', - position: [26, 26] + code: 'invalid-continuing-block-placement', + message: + '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)', + position: [21, 21] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/then-before-closing/input.svelte b/packages/svelte/tests/compiler-errors/samples/then-before-closing/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/then-before-closing/input.svelte rename to packages/svelte/tests/compiler-errors/samples/then-before-closing/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/then-without-await/_config.js b/packages/svelte/tests/compiler-errors/samples/then-without-await/_config.js index 36f9cd92e..64c85f85c 100644 --- a/packages/svelte/tests/compiler-errors/samples/then-without-await/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/then-without-await/_config.js @@ -2,8 +2,9 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-then-placement', - message: 'Cannot have an {:then} block outside an {#await ...} block', - position: [6, 6] + code: 'invalid-continuing-block-placement', + message: + '{:...} block is invalid at this position (did you forget to close the preceeding element or block?)', + position: [1, 1] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/then-without-await/input.svelte b/packages/svelte/tests/compiler-errors/samples/then-without-await/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/then-without-await/input.svelte rename to packages/svelte/tests/compiler-errors/samples/then-without-await/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/_config.js b/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/_config.js index a11d5d365..2f4ec8835 100644 --- a/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/_config.js @@ -3,7 +3,7 @@ import { test } from '../../test'; export default test({ error: { code: 'unclosed-attribute-value', - message: 'Expected to close the attribute value with }', - position: [25, 25] + message: 'Expected closing } character', + position: [19, 19] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/input.svelte b/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/input.svelte deleted file mode 100644 index 609b5532f..000000000 --- a/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/input.svelte +++ /dev/null @@ -1 +0,0 @@ -<Component test={{a: 1} /> \ No newline at end of file diff --git a/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/main.svelte b/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/main.svelte new file mode 100644 index 000000000..3166782f6 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/unclosed-attribute-self-close-tag/main.svelte @@ -0,0 +1 @@ +<Component test={ /> \ No newline at end of file diff --git a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-b/input.svelte b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-b/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-b/input.svelte rename to packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-b/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-c/input.svelte b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-c/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-c/input.svelte rename to packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-c/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/_config.js b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/_config.js index 44a3558a5..b10de772d 100644 --- a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/_config.js @@ -4,6 +4,6 @@ export default test({ error: { code: 'unclosed-block', message: 'Block was left open', - position: [0, 0] + position: [0, 1] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/input.svelte b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/input.svelte rename to packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input-d/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/_config.js b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/_config.js index 3b7864cad..8ff85b724 100644 --- a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/_config.js @@ -4,6 +4,6 @@ export default test({ error: { code: 'unclosed-element', message: '<div> was left open', - position: [0, 0] + position: [0, 1] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/input.svelte b/packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/input.svelte rename to packages/svelte/tests/compiler-errors/samples/unexpected-end-of-input/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose-2/input.svelte b/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose-2/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose-2/input.svelte rename to packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose-2/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose/_config.js b/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose/_config.js index 341a46831..b485095bc 100644 --- a/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-closing-tag', - message: '</p> attempted to close <p> that was already automatically closed by <pre>', + code: 'invalid-closing-tag-after-autoclose', + message: '</p> attempted to close element that was already automatically closed by <pre>', position: [24, 24] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose/input.svelte b/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose/input.svelte rename to packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag-autoclose/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag/input.svelte b/packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag/input.svelte rename to packages/svelte/tests/compiler-errors/samples/unmatched-closing-tag/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/void-closing/_config.js b/packages/svelte/tests/compiler-errors/samples/void-closing/_config.js index 2c9f9fad2..704d8c602 100644 --- a/packages/svelte/tests/compiler-errors/samples/void-closing/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/void-closing/_config.js @@ -3,7 +3,7 @@ import { test } from '../../test'; export default test({ error: { code: 'invalid-void-content', - message: '<input> is a void element and cannot have children, or a closing tag', + message: 'Void elements cannot have children or closing tags', position: [23, 23] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/void-closing/input.svelte b/packages/svelte/tests/compiler-errors/samples/void-closing/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/void-closing/input.svelte rename to packages/svelte/tests/compiler-errors/samples/void-closing/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/window-children/_config.js b/packages/svelte/tests/compiler-errors/samples/window-children/_config.js index 794381c93..33b57bf5f 100644 --- a/packages/svelte/tests/compiler-errors/samples/window-children/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/window-children/_config.js @@ -2,7 +2,7 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-window-content', + code: 'invalid-element-content', message: '<svelte:window> cannot have children', position: [15, 15] } diff --git a/packages/svelte/tests/compiler-errors/samples/window-children/input.svelte b/packages/svelte/tests/compiler-errors/samples/window-children/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/window-children/input.svelte rename to packages/svelte/tests/compiler-errors/samples/window-children/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/window-duplicate/_config.js b/packages/svelte/tests/compiler-errors/samples/window-duplicate/_config.js index 3eff2d3e9..9c068c029 100644 --- a/packages/svelte/tests/compiler-errors/samples/window-duplicate/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/window-duplicate/_config.js @@ -2,8 +2,8 @@ import { test } from '../../test'; export default test({ error: { - code: 'duplicate-window', - message: 'A component can only have one <svelte:window> tag', + code: 'duplicate-svelte-element', + message: 'A component can only have one <svelte:window> element', position: [17, 17] } }); diff --git a/packages/svelte/tests/compiler-errors/samples/window-duplicate/input.svelte b/packages/svelte/tests/compiler-errors/samples/window-duplicate/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/window-duplicate/input.svelte rename to packages/svelte/tests/compiler-errors/samples/window-duplicate/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/window-inside-block/_config.js b/packages/svelte/tests/compiler-errors/samples/window-inside-block/_config.js index f47b6be7e..698c3813e 100644 --- a/packages/svelte/tests/compiler-errors/samples/window-inside-block/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/window-inside-block/_config.js @@ -2,7 +2,7 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-window-placement', + code: 'invalid-svelte-element-placement', message: '<svelte:window> tags cannot be inside elements or blocks', position: [11, 11] } diff --git a/packages/svelte/tests/compiler-errors/samples/window-inside-block/input.svelte b/packages/svelte/tests/compiler-errors/samples/window-inside-block/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/window-inside-block/input.svelte rename to packages/svelte/tests/compiler-errors/samples/window-inside-block/main.svelte diff --git a/packages/svelte/tests/compiler-errors/samples/window-inside-element/_config.js b/packages/svelte/tests/compiler-errors/samples/window-inside-element/_config.js index e4411b8df..04aba56e5 100644 --- a/packages/svelte/tests/compiler-errors/samples/window-inside-element/_config.js +++ b/packages/svelte/tests/compiler-errors/samples/window-inside-element/_config.js @@ -2,7 +2,7 @@ import { test } from '../../test'; export default test({ error: { - code: 'invalid-window-placement', + code: 'invalid-svelte-element-placement', message: '<svelte:window> tags cannot be inside elements or blocks', position: [7, 7] } diff --git a/packages/svelte/tests/compiler-errors/samples/window-inside-element/input.svelte b/packages/svelte/tests/compiler-errors/samples/window-inside-element/main.svelte similarity index 100% rename from packages/svelte/tests/compiler-errors/samples/window-inside-element/input.svelte rename to packages/svelte/tests/compiler-errors/samples/window-inside-element/main.svelte diff --git a/packages/svelte/tests/compiler-errors/test.ts b/packages/svelte/tests/compiler-errors/test.ts index b7a76c896..ea696890b 100644 --- a/packages/svelte/tests/compiler-errors/test.ts +++ b/packages/svelte/tests/compiler-errors/test.ts @@ -2,6 +2,7 @@ import * as fs from 'node:fs'; import { assert, expect } from 'vitest'; import { compile, compileModule, type CompileError } from 'svelte/compiler'; import { suite, type BaseTest } from '../suite'; +import { read_file } from '../helpers.js'; interface CompilerErrorTest extends BaseTest { error: { @@ -12,11 +13,15 @@ interface CompilerErrorTest extends BaseTest { } const { test, run } = suite<CompilerErrorTest>((config, cwd) => { + if (!fs.existsSync(`${cwd}/main.svelte`) && !fs.existsSync(`${cwd}/main.svelte.js`)) { + throw new Error('Expected main.svelte or main.svelte.js'); + } + if (fs.existsSync(`${cwd}/main.svelte`)) { let caught_error = false; try { - compile(fs.readFileSync(`${cwd}/main.svelte`, 'utf-8'), { + compile(read_file(`${cwd}/main.svelte`), { generate: 'client' }); } catch (e) { @@ -37,11 +42,11 @@ const { test, run } = suite<CompilerErrorTest>((config, cwd) => { } } - if (fs.existsSync(`${cwd}/main.js`)) { + if (fs.existsSync(`${cwd}/main.svelte.js`)) { let caught_error = false; try { - compileModule(fs.readFileSync(`${cwd}/main.js`, 'utf-8'), { + compileModule(read_file(`${cwd}/main.svelte.js`), { generate: 'client' }); } catch (e) { @@ -49,8 +54,12 @@ const { test, run } = suite<CompilerErrorTest>((config, cwd) => { caught_error = true; - expect(error.code).toMatch(config.error.code); - expect(error.message).toMatch(config.error.message); + expect(error.code).toEqual(config.error.code); + expect(error.message).toEqual(config.error.message); + + if (config.error.position) { + expect(error.position).toEqual(config.error.position); + } } if (!caught_error) { diff --git a/packages/svelte/tests/helpers.js b/packages/svelte/tests/helpers.js index 46f70b011..a8e3d605d 100644 --- a/packages/svelte/tests/helpers.js +++ b/packages/svelte/tests/helpers.js @@ -20,13 +20,20 @@ export function try_load_json(file) { */ export function try_read_file(file) { try { - return fs.readFileSync(file, 'utf-8').replace(/\r\n/g, '\n'); + return read_file(file); } catch (err) { if (/** @type {any} */ (err).code !== 'ENOENT') throw err; return null; } } +/** + * @param {string} file + */ +export function read_file(file) { + return fs.readFileSync(file, 'utf-8').replace(/\r\n/g, '\n'); +} + export function create_deferred() { /** @param {any} [value] */ let resolve = (value) => {}; diff --git a/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_before.html b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_before.html new file mode 100644 index 000000000..afeffd5eb --- /dev/null +++ b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_before.html @@ -0,0 +1 @@ +<!--ssr:0--><a href="/bar">foo</a><!--ssr:0--> diff --git a/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_config.js b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_config.js new file mode 100644 index 000000000..cc24163f2 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + test(assert, target) { + assert.equal(target.querySelector('a')?.getAttribute('href'), '/bar'); + } +}); diff --git a/packages/svelte/tests/hydration/samples/ignore-mismatched-href/main.svelte b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/main.svelte new file mode 100644 index 000000000..5dcd4d400 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/ignore-mismatched-href/main.svelte @@ -0,0 +1,5 @@ +<script> + let browser = typeof window !== 'undefined'; +</script> + +<a href={browser ? '/foo': '/bar'}>foo</a> diff --git a/packages/svelte/tests/parser-legacy/samples/action-duplicate/output.json b/packages/svelte/tests/parser-legacy/samples/action-duplicate/output.json index 99e9a0d15..3dad9bb4e 100644 --- a/packages/svelte/tests/parser-legacy/samples/action-duplicate/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action-duplicate/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 35, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 35, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-call/output.json b/packages/svelte/tests/parser-legacy/samples/action-with-call/output.json index f1d529374..66ce187c6 100644 --- a/packages/svelte/tests/parser-legacy/samples/action-with-call/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action-with-call/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 40, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 40, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-identifier/output.json b/packages/svelte/tests/parser-legacy/samples/action-with-identifier/output.json index 4931f29ea..39a6f5f64 100644 --- a/packages/svelte/tests/parser-legacy/samples/action-with-identifier/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action-with-identifier/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 29, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 29, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-literal/output.json b/packages/svelte/tests/parser-legacy/samples/action-with-literal/output.json index e59e5e2d7..94c60b701 100644 --- a/packages/svelte/tests/parser-legacy/samples/action-with-literal/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action-with-literal/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 37, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 37, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/action/output.json b/packages/svelte/tests/parser-legacy/samples/action/output.json index 0ed95d4ee..d72bf7db1 100644 --- a/packages/svelte/tests/parser-legacy/samples/action/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 21, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 21, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/animation/output.json b/packages/svelte/tests/parser-legacy/samples/animation/output.json index 3f967587f..6a43c13c2 100644 --- a/packages/svelte/tests/parser-legacy/samples/animation/output.json +++ b/packages/svelte/tests/parser-legacy/samples/animation/output.json @@ -1,34 +1,18 @@ { "html": { + "type": "Fragment", "start": 0, "end": 70, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 70, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 13, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 13 - } - }, - "name": "things" - }, "children": [ { + "type": "Element", "start": 33, "end": 62, - "type": "Element", "name": "div", "attributes": [ { @@ -42,9 +26,9 @@ ], "children": [ { + "type": "Text", "start": 51, "end": 56, - "type": "Text", "raw": "flips", "data": "flips" } @@ -57,6 +41,22 @@ "start": 17, "end": 22 }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 13 + } + }, + "name": "things" + }, "key": { "type": "Identifier", "start": 24, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-class-directive/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-class-directive/output.json index 88708ebb6..9efe9acf8 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-class-directive/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-class-directive/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 29, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 29, - "type": "Element", "name": "div", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-containing-solidus/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-containing-solidus/output.json index a02fb12ae..2c63b3a43 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-containing-solidus/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-containing-solidus/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 41, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 41, - "type": "Element", "name": "a", "attributes": [ { + "type": "Attribute", "start": 3, "end": 30, - "type": "Attribute", "name": "href", "value": [ { @@ -28,9 +28,9 @@ ], "children": [ { + "type": "Text", "start": 31, "end": 37, - "type": "Text", "raw": "Google", "data": "Google" } diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-curly-bracket/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-curly-bracket/output.json index 077e307df..2453dc9e0 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-curly-bracket/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-curly-bracket/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 18, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 18, - "type": "Element", "name": "input", "attributes": [ { + "type": "Attribute", "start": 7, "end": 15, - "type": "Attribute", "name": "foo", "value": [ { @@ -24,9 +24,9 @@ "data": "a" }, { + "type": "MustacheTag", "start": 12, "end": 15, - "type": "MustacheTag", "expression": { "type": "Literal", "start": 13, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-dynamic-boolean/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-dynamic-boolean/output.json index b87a65467..5793afe89 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-dynamic-boolean/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-dynamic-boolean/output.json @@ -1,25 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 41, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 41, - "type": "Element", "name": "textarea", "attributes": [ { + "type": "Attribute", "start": 10, "end": 29, - "type": "Attribute", "name": "readonly", "value": [ { + "type": "MustacheTag", "start": 19, "end": 29, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 20, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-dynamic/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-dynamic/output.json index 4926b645a..9fd98c80e 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-dynamic/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-dynamic/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 42, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 42, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 28, - "type": "Attribute", "name": "style", "value": [ { @@ -24,9 +24,9 @@ "data": "color: " }, { + "type": "MustacheTag", "start": 19, "end": 26, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 20, @@ -56,9 +56,9 @@ ], "children": [ { + "type": "MustacheTag", "start": 29, "end": 36, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 30, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-empty/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-empty/output.json index 04b28f8d4..d2a3dcd93 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-empty/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-empty/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 38, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 38, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 9, - "type": "Attribute", "name": "a", "value": [ { @@ -26,15 +26,15 @@ ] }, { + "type": "Attribute", "start": 10, "end": 16, - "type": "Attribute", "name": "b", "value": [ { + "type": "MustacheTag", "start": 12, "end": 16, - "type": "MustacheTag", "expression": { "type": "Literal", "start": 13, @@ -56,9 +56,9 @@ ] }, { + "type": "Attribute", "start": 17, "end": 21, - "type": "Attribute", "name": "c", "value": [ { @@ -71,15 +71,15 @@ ] }, { + "type": "Attribute", "start": 22, "end": 30, - "type": "Attribute", "name": "d", "value": [ { + "type": "MustacheTag", "start": 25, "end": 29, - "type": "MustacheTag", "expression": { "type": "Literal", "start": 26, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-escaped/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-escaped/output.json index 71eb553a7..e2eb99f32 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-escaped/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-escaped/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 83, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 83, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 76, - "type": "Attribute", "name": "data-foo", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-multiple/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-multiple/output.json index ce098fa53..66b780e53 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-multiple/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-multiple/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 28, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 28, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 11, - "type": "Attribute", "name": "id", "value": [ { @@ -26,9 +26,9 @@ ] }, { + "type": "Attribute", "start": 12, "end": 21, - "type": "Attribute", "name": "class", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json index 4bfb7af18..5daf29018 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json @@ -1,25 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 11, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 11, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 9, - "type": "Attribute", "name": "id", "value": [ { + "type": "AttributeShorthand", "start": 6, "end": 8, - "type": "AttributeShorthand", "expression": { "start": 6, "end": 8, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-static-boolean/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-static-boolean/output.json index cf65d5459..8cb93b75e 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-static-boolean/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-static-boolean/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 30, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 30, - "type": "Element", "name": "textarea", "attributes": [ { + "type": "Attribute", "start": 10, "end": 18, - "type": "Attribute", "name": "readonly", "value": true } diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-static/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-static/output.json index b4fff2cd0..3e19a4727 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-static/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-static/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 23, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 23, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 16, - "type": "Attribute", "name": "class", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-modifiers/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-modifiers/output.json index 1587c2a3f..b7de71ff5 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-modifiers/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-modifiers/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 43, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 43, - "type": "Element", "name": "div", "attributes": [ { @@ -18,9 +18,9 @@ "modifiers": ["important"], "value": [ { + "type": "MustacheTag", "start": 27, "end": 36, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 28, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-shorthand/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-shorthand/output.json index 80fb4f741..d7f53cb00 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-shorthand/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-shorthand/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 23, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 23, - "type": "Element", "name": "div", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-string/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-string/output.json index 505e9333e..5acf7d797 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-string/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-string/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 252, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 29, - "type": "Element", "name": "div", "attributes": [ { @@ -30,24 +30,24 @@ "children": [] }, { + "type": "Text", "start": 29, "end": 30, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 30, "end": 59, - "type": "Element", "name": "div", "attributes": [ { "start": 35, "end": 52, "type": "StyleDirective", - "modifiers": [], "name": "color", + "modifiers": [], "value": [ { "start": 48, @@ -62,16 +62,16 @@ "children": [] }, { + "type": "Text", "start": 59, "end": 60, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 60, "end": 87, - "type": "Element", "name": "div", "attributes": [ { @@ -94,16 +94,16 @@ "children": [] }, { + "type": "Text", "start": 87, "end": 88, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 88, "end": 127, - "type": "Element", "name": "div", "attributes": [ { @@ -121,9 +121,9 @@ "data": "red" }, { + "type": "MustacheTag", "start": 109, "end": 119, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 110, @@ -147,16 +147,16 @@ "children": [] }, { + "type": "Text", "start": 127, "end": 128, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 128, "end": 167, - "type": "Element", "name": "div", "attributes": [ { @@ -174,9 +174,9 @@ "data": "red" }, { + "type": "MustacheTag", "start": 149, "end": 159, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 150, @@ -200,16 +200,16 @@ "children": [] }, { + "type": "Text", "start": 167, "end": 168, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 168, "end": 205, - "type": "Element", "name": "div", "attributes": [ { @@ -227,9 +227,9 @@ "data": "red" }, { + "type": "MustacheTag", "start": 188, "end": 198, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 189, @@ -253,16 +253,16 @@ "children": [] }, { + "type": "Text", "start": 205, "end": 206, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 206, "end": 252, - "type": "Element", "name": "div", "attributes": [ { @@ -273,9 +273,9 @@ "modifiers": [], "value": [ { + "type": "MustacheTag", "start": 223, "end": 245, - "type": "MustacheTag", "expression": { "type": "TemplateLiteral", "start": 224, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive/output.json index b7983a769..2cce9fef9 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 33, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 33, - "type": "Element", "name": "div", "attributes": [ { @@ -18,9 +18,9 @@ "modifiers": [], "value": [ { + "type": "MustacheTag", "start": 17, "end": 26, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 18, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style/output.json index 8dd604523..1d9a528d6 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 34, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 34, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 24, - "type": "Attribute", "name": "style", "value": [ { @@ -28,9 +28,9 @@ ], "children": [ { + "type": "Text", "start": 25, "end": 28, - "type": "Text", "raw": "red", "data": "red" } diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json index 582ca0a2f..5df4d66ab 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 21, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 21, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 14, - "type": "Attribute", "name": "class", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-with-whitespace/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-with-whitespace/output.json index dbd1d8d60..4d3a29180 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-with-whitespace/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-with-whitespace/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 38, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 38, - "type": "Element", "name": "button", "attributes": [ { @@ -36,9 +36,9 @@ ], "children": [ { + "type": "Text", "start": 24, "end": 29, - "type": "Text", "raw": "Click", "data": "Click" } diff --git a/packages/svelte/tests/parser-legacy/samples/await-catch/output.json b/packages/svelte/tests/parser-legacy/samples/await-catch/output.json index 163d06130..06a73d522 100644 --- a/packages/svelte/tests/parser-legacy/samples/await-catch/output.json +++ b/packages/svelte/tests/parser-legacy/samples/await-catch/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 99, - "type": "Fragment", "children": [ { + "type": "AwaitBlock", "start": 0, "end": 99, - "type": "AwaitBlock", "expression": { "type": "Identifier", "start": 8, @@ -32,37 +32,37 @@ "end": 55 }, "pending": { + "type": "PendingBlock", "start": 19, "end": 39, - "type": "PendingBlock", "children": [ { + "type": "Text", "start": 19, "end": 21, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 21, "end": 38, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 24, "end": 34, - "type": "Text", "raw": "loading...", "data": "loading..." } ] }, { + "type": "Text", "start": 38, "end": 39, - "type": "Text", "raw": "\n", "data": "\n" } @@ -70,42 +70,42 @@ "skip": false }, "then": { + "type": "ThenBlock", "start": null, "end": null, - "type": "ThenBlock", "children": [], "skip": true }, "catch": { + "type": "CatchBlock", "start": 39, "end": 91, - "type": "CatchBlock", "children": [ { + "type": "Text", "start": 56, "end": 58, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 58, "end": 90, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 61, "end": 68, - "type": "Text", "raw": "oh no! ", "data": "oh no! " }, { + "type": "MustacheTag", "start": 68, "end": 86, - "type": "MustacheTag", "expression": { "type": "MemberExpression", "start": 69, @@ -159,9 +159,9 @@ ] }, { + "type": "Text", "start": 90, "end": 91, - "type": "Text", "raw": "\n", "data": "\n" } diff --git a/packages/svelte/tests/parser-legacy/samples/await-then-catch/output.json b/packages/svelte/tests/parser-legacy/samples/await-then-catch/output.json index 18b484b0c..a2ccb995e 100644 --- a/packages/svelte/tests/parser-legacy/samples/await-then-catch/output.json +++ b/packages/svelte/tests/parser-legacy/samples/await-then-catch/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 148, - "type": "Fragment", "children": [ { + "type": "AwaitBlock", "start": 0, "end": 148, - "type": "AwaitBlock", "expression": { "type": "Identifier", "start": 8, @@ -37,37 +37,37 @@ "end": 104 }, "pending": { + "type": "PendingBlock", "start": 19, "end": 39, - "type": "PendingBlock", "children": [ { + "type": "Text", "start": 19, "end": 21, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 21, "end": 38, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 24, "end": 34, - "type": "Text", "raw": "loading...", "data": "loading..." } ] }, { + "type": "Text", "start": 38, "end": 39, - "type": "Text", "raw": "\n", "data": "\n" } @@ -75,35 +75,35 @@ "skip": false }, "then": { + "type": "ThenBlock", "start": 39, "end": 88, - "type": "ThenBlock", "children": [ { + "type": "Text", "start": 55, "end": 57, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 57, "end": 87, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 60, "end": 73, - "type": "Text", "raw": "the value is ", "data": "the value is " }, { + "type": "MustacheTag", "start": 73, "end": 83, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 74, @@ -124,9 +124,9 @@ ] }, { + "type": "Text", "start": 87, "end": 88, - "type": "Text", "raw": "\n", "data": "\n" } @@ -134,35 +134,35 @@ "skip": false }, "catch": { + "type": "CatchBlock", "start": 88, "end": 140, - "type": "CatchBlock", "children": [ { + "type": "Text", "start": 105, "end": 107, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 107, "end": 139, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 110, "end": 117, - "type": "Text", "raw": "oh no! ", "data": "oh no! " }, { + "type": "MustacheTag", "start": 117, "end": 135, - "type": "MustacheTag", "expression": { "type": "MemberExpression", "start": 118, @@ -216,9 +216,9 @@ ] }, { + "type": "Text", "start": 139, "end": 140, - "type": "Text", "raw": "\n", "data": "\n" } diff --git a/packages/svelte/tests/parser-legacy/samples/binding-shorthand/output.json b/packages/svelte/tests/parser-legacy/samples/binding-shorthand/output.json index a86d3f635..672014629 100644 --- a/packages/svelte/tests/parser-legacy/samples/binding-shorthand/output.json +++ b/packages/svelte/tests/parser-legacy/samples/binding-shorthand/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 30, "end": 48, - "type": "Fragment", "children": [ { + "type": "Text", "start": 28, "end": 30, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "InlineComponent", "start": 30, "end": 48, - "type": "InlineComponent", "name": "Widget", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/binding/output.json b/packages/svelte/tests/parser-legacy/samples/binding/output.json index 3dad94d3d..4ce069bd3 100644 --- a/packages/svelte/tests/parser-legacy/samples/binding/output.json +++ b/packages/svelte/tests/parser-legacy/samples/binding/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 31, "end": 56, - "type": "Fragment", "children": [ { + "type": "Text", "start": 29, "end": 31, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "Element", "start": 31, "end": 56, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/comment-with-ignores/output.json b/packages/svelte/tests/parser-legacy/samples/comment-with-ignores/output.json index e15150a09..b149436a2 100644 --- a/packages/svelte/tests/parser-legacy/samples/comment-with-ignores/output.json +++ b/packages/svelte/tests/parser-legacy/samples/comment-with-ignores/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 30, - "type": "Fragment", "children": [ { + "type": "Comment", "start": 0, "end": 30, - "type": "Comment", "data": " svelte-ignore foo bar ", "ignores": ["foo", "bar"] } diff --git a/packages/svelte/tests/parser-legacy/samples/comment/output.json b/packages/svelte/tests/parser-legacy/samples/comment/output.json index b3f050d65..6017db404 100644 --- a/packages/svelte/tests/parser-legacy/samples/comment/output.json +++ b/packages/svelte/tests/parser-legacy/samples/comment/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 18, - "type": "Fragment", "children": [ { + "type": "Comment", "start": 0, "end": 18, - "type": "Comment", "data": " a comment ", "ignores": [] } diff --git a/packages/svelte/tests/parser-legacy/samples/component-dynamic/output.json b/packages/svelte/tests/parser-legacy/samples/component-dynamic/output.json index 06e17f809..f624a04c6 100644 --- a/packages/svelte/tests/parser-legacy/samples/component-dynamic/output.json +++ b/packages/svelte/tests/parser-legacy/samples/component-dynamic/output.json @@ -1,16 +1,14 @@ { "html": { + "type": "Fragment", "start": 0, "end": 62, - "type": "Fragment", "children": [ { - "start": 0, - "end": 62, "type": "InlineComponent", "name": "svelte:component", - "attributes": [], - "children": [], + "start": 0, + "end": 62, "expression": { "type": "ConditionalExpression", "start": 25, @@ -73,7 +71,9 @@ }, "name": "Bar" } - } + }, + "attributes": [], + "children": [] } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/convert-entities-in-element/output.json b/packages/svelte/tests/parser-legacy/samples/convert-entities-in-element/output.json index 15784cbcb..f7a8a3c67 100644 --- a/packages/svelte/tests/parser-legacy/samples/convert-entities-in-element/output.json +++ b/packages/svelte/tests/parser-legacy/samples/convert-entities-in-element/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 0, "end": 24, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 24, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 3, "end": 20, - "type": "Text", "raw": "Hello & World", "data": "Hello & World" } diff --git a/packages/svelte/tests/parser-legacy/samples/convert-entities/output.json b/packages/svelte/tests/parser-legacy/samples/convert-entities/output.json index 5a3ad64c2..c336a3978 100644 --- a/packages/svelte/tests/parser-legacy/samples/convert-entities/output.json +++ b/packages/svelte/tests/parser-legacy/samples/convert-entities/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 17, - "type": "Fragment", "children": [ { + "type": "Text", "start": 0, "end": 17, - "type": "Text", "raw": "Hello & World", "data": "Hello & World" } diff --git a/packages/svelte/tests/parser-legacy/samples/css/output.json b/packages/svelte/tests/parser-legacy/samples/css/output.json index 31d3d145a..d1256eef4 100644 --- a/packages/svelte/tests/parser-legacy/samples/css/output.json +++ b/packages/svelte/tests/parser-legacy/samples/css/output.json @@ -1,29 +1,29 @@ { "html": { + "type": "Fragment", "start": 0, "end": 14, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 14, - "type": "Element", "name": "div", "attributes": [], "children": [ { + "type": "Text", "start": 5, "end": 8, - "type": "Text", "raw": "foo", "data": "foo" } ] }, { + "type": "Text", "start": 14, "end": 16, - "type": "Text", "raw": "\n\n", "data": "\n\n" } @@ -39,9 +39,13 @@ "type": "Rule", "prelude": { "type": "SelectorList", + "start": 25, + "end": 28, "children": [ { "type": "Selector", + "start": 25, + "end": 28, "children": [ { "type": "TypeSelector", @@ -49,27 +53,23 @@ "start": 25, "end": 28 } - ], - "start": 25, - "end": 28 + ] } - ], - "start": 25, - "end": 28 + ] }, "block": { "type": "Block", + "start": 29, + "end": 47, "children": [ { "type": "Declaration", - "property": "color", - "value": "red", "start": 33, - "end": 43 + "end": 43, + "property": "color", + "value": "red" } - ], - "start": 29, - "end": 47 + ] }, "start": 25, "end": 47 diff --git a/packages/svelte/tests/parser-legacy/samples/dynamic-element-string/output.json b/packages/svelte/tests/parser-legacy/samples/dynamic-element-string/output.json index 9e9245b1a..6dd18f0a4 100644 --- a/packages/svelte/tests/parser-legacy/samples/dynamic-element-string/output.json +++ b/packages/svelte/tests/parser-legacy/samples/dynamic-element-string/output.json @@ -1,30 +1,31 @@ { "html": { + "type": "Fragment", "start": 0, "end": 101, - "type": "Fragment", "children": [ { - "start": 0, - "end": 44, "type": "Element", "name": "svelte:element", + "start": 0, + "end": 44, + "tag": "div", "attributes": [], - "children": [], - "tag": "div" + "children": [] }, { "type": "Text", "start": 44, "end": 45, - "data": "\n", - "raw": "\n" + "raw": "\n", + "data": "\n" }, { - "start": 45, - "end": 101, "type": "Element", "name": "svelte:element", + "start": 45, + "end": 101, + "tag": "div", "attributes": [ { "type": "Attribute", @@ -33,17 +34,16 @@ "name": "class", "value": [ { - "type": "Text", "start": 79, "end": 82, - "data": "foo", - "raw": "foo" + "type": "Text", + "raw": "foo", + "data": "foo" } ] } ], - "children": [], - "tag": "div" + "children": [] } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/dynamic-element-variable/output.json b/packages/svelte/tests/parser-legacy/samples/dynamic-element-variable/output.json index 53769e94e..291cdaa73 100644 --- a/packages/svelte/tests/parser-legacy/samples/dynamic-element-variable/output.json +++ b/packages/svelte/tests/parser-legacy/samples/dynamic-element-variable/output.json @@ -1,17 +1,16 @@ { "html": { + "type": "Fragment", "start": 0, "end": 101, - "type": "Fragment", "children": [ { - "start": 0, - "end": 44, "type": "Element", "name": "svelte:element", - "attributes": [], - "children": [], + "start": 0, + "end": 44, "tag": { + "type": "Identifier", "start": 22, "end": 25, "loc": { @@ -24,23 +23,39 @@ "column": 25 } }, - "name": "tag", - "type": "Identifier" - } + "name": "tag" + }, + "attributes": [], + "children": [] }, { "type": "Text", "start": 44, "end": 45, - "data": "\n", - "raw": "\n" + "raw": "\n", + "data": "\n" }, { - "start": 45, - "end": 101, "type": "Element", "name": "svelte:element", - "children": [], + "start": 45, + "end": 101, + "tag": { + "type": "Identifier", + "start": 67, + "end": 70, + "loc": { + "start": { + "line": 2, + "column": 22 + }, + "end": { + "line": 2, + "column": 25 + } + }, + "name": "tag" + }, "attributes": [ { "type": "Attribute", @@ -49,31 +64,16 @@ "name": "class", "value": [ { - "type": "Text", "start": 79, "end": 82, - "data": "foo", - "raw": "foo" + "type": "Text", + "raw": "foo", + "data": "foo" } ] } ], - "tag": { - "start": 67, - "end": 70, - "loc": { - "start": { - "line": 2, - "column": 22 - }, - "end": { - "line": 2, - "column": 25 - } - }, - "name": "tag", - "type": "Identifier" - } + "children": [] } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/dynamic-import/output.json b/packages/svelte/tests/parser-legacy/samples/dynamic-import/output.json index 14ae36ae3..a439b65dd 100644 --- a/packages/svelte/tests/parser-legacy/samples/dynamic-import/output.json +++ b/packages/svelte/tests/parser-legacy/samples/dynamic-import/output.json @@ -1,8 +1,8 @@ { "html": { + "type": "Fragment", "start": null, "end": null, - "type": "Fragment", "children": [] }, "instance": { diff --git a/packages/svelte/tests/parser-legacy/samples/each-block-destructured/output.json b/packages/svelte/tests/parser-legacy/samples/each-block-destructured/output.json index 9cbeb2be6..d19f5cbbf 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block-destructured/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block-destructured/output.json @@ -1,48 +1,32 @@ { "html": { + "type": "Fragment", "start": 41, "end": 112, - "type": "Fragment", "children": [ { + "type": "Text", "start": 39, "end": 41, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "EachBlock", "start": 41, "end": 112, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 48, - "end": 55, - "loc": { - "start": { - "line": 5, - "column": 7 - }, - "end": { - "line": 5, - "column": 14 - } - }, - "name": "animals" - }, "children": [ { + "type": "Element", "start": 83, "end": 104, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 86, "end": 91, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 87, @@ -61,16 +45,16 @@ } }, { + "type": "Text", "start": 91, "end": 93, - "type": "Text", "raw": ": ", "data": ": " }, { + "type": "MustacheTag", "start": 93, "end": 100, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 94, @@ -170,6 +154,22 @@ } } ] + }, + "expression": { + "type": "Identifier", + "start": 48, + "end": 55, + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 14 + } + }, + "name": "animals" } } ] diff --git a/packages/svelte/tests/parser-legacy/samples/each-block-else/output.json b/packages/svelte/tests/parser-legacy/samples/each-block-else/output.json index 935195985..5af3bff86 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block-else/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block-else/output.json @@ -1,41 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 77, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 77, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 14, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 14 - } - }, - "name": "animals" - }, "children": [ { + "type": "Element", "start": 27, "end": 42, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 30, "end": 38, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 31, @@ -62,22 +46,38 @@ "start": 18, "end": 24 }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 14 + } + }, + "name": "animals" + }, "else": { + "type": "ElseBlock", "start": 50, "end": 70, - "type": "ElseBlock", "children": [ { + "type": "Element", "start": 52, "end": 69, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 55, "end": 65, - "type": "Text", "raw": "no animals", "data": "no animals" } diff --git a/packages/svelte/tests/parser-legacy/samples/each-block-indexed/output.json b/packages/svelte/tests/parser-legacy/samples/each-block-indexed/output.json index ff5d57432..915dd6228 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block-indexed/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block-indexed/output.json @@ -1,41 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 58, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 58, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 14, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 14 - } - }, - "name": "animals" - }, "children": [ { + "type": "Element", "start": 30, "end": 50, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 33, "end": 36, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 34, @@ -54,16 +38,16 @@ } }, { + "type": "Text", "start": 36, "end": 38, - "type": "Text", "raw": ": ", "data": ": " }, { + "type": "MustacheTag", "start": 38, "end": 46, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 39, @@ -90,6 +74,22 @@ "start": 18, "end": 24 }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 14 + } + }, + "name": "animals" + }, "index": "i" } ] diff --git a/packages/svelte/tests/parser-legacy/samples/each-block-keyed/output.json b/packages/svelte/tests/parser-legacy/samples/each-block-keyed/output.json index e4e968985..9582d4546 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block-keyed/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block-keyed/output.json @@ -1,41 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 54, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 54, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 12, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 12 - } - }, - "name": "todos" - }, "children": [ { + "type": "Element", "start": 33, "end": 46, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 36, "end": 42, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 37, @@ -62,6 +46,22 @@ "start": 16, "end": 20 }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "todos" + }, "key": { "type": "MemberExpression", "start": 22, diff --git a/packages/svelte/tests/parser-legacy/samples/each-block/output.json b/packages/svelte/tests/parser-legacy/samples/each-block/output.json index 39373ea2a..1b418dac5 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block/output.json @@ -1,41 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 50, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 50, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 14, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 14 - } - }, - "name": "animals" - }, "children": [ { + "type": "Element", "start": 27, "end": 42, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 30, "end": 38, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 31, @@ -61,6 +45,22 @@ "name": "animal", "start": 18, "end": 24 + }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 14 + } + }, + "name": "animals" } } ] diff --git a/packages/svelte/tests/parser-legacy/samples/element-with-attribute-empty-string/output.json b/packages/svelte/tests/parser-legacy/samples/element-with-attribute-empty-string/output.json index 8e8768eb6..7773256d4 100644 --- a/packages/svelte/tests/parser-legacy/samples/element-with-attribute-empty-string/output.json +++ b/packages/svelte/tests/parser-legacy/samples/element-with-attribute-empty-string/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 43, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 21, - "type": "Element", "name": "span", "attributes": [ { + "type": "Attribute", "start": 6, "end": 13, - "type": "Attribute", "name": "attr", "value": [ { @@ -29,22 +29,22 @@ "children": [] }, { + "type": "Text", "start": 21, "end": 22, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 22, "end": 43, - "type": "Element", "name": "span", "attributes": [ { + "type": "Attribute", "start": 28, "end": 35, - "type": "Attribute", "name": "attr", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/element-with-attribute/output.json b/packages/svelte/tests/parser-legacy/samples/element-with-attribute/output.json index a80344101..9477886bb 100644 --- a/packages/svelte/tests/parser-legacy/samples/element-with-attribute/output.json +++ b/packages/svelte/tests/parser-legacy/samples/element-with-attribute/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 49, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 24, - "type": "Element", "name": "span", "attributes": [ { + "type": "Attribute", "start": 6, "end": 16, - "type": "Attribute", "name": "attr", "value": [ { @@ -29,22 +29,22 @@ "children": [] }, { + "type": "Text", "start": 24, "end": 25, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 25, "end": 49, - "type": "Element", "name": "span", "attributes": [ { + "type": "Attribute", "start": 31, "end": 41, - "type": "Attribute", "name": "attr", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/element-with-mustache/output.json b/packages/svelte/tests/parser-legacy/samples/element-with-mustache/output.json index cb4ededf0..ce0dc25e8 100644 --- a/packages/svelte/tests/parser-legacy/samples/element-with-mustache/output.json +++ b/packages/svelte/tests/parser-legacy/samples/element-with-mustache/output.json @@ -1,27 +1,27 @@ { "html": { + "type": "Fragment", "start": 0, "end": 22, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 22, - "type": "Element", "name": "h1", "attributes": [], "children": [ { + "type": "Text", "start": 4, "end": 10, - "type": "Text", "raw": "hello ", "data": "hello " }, { + "type": "MustacheTag", "start": 10, "end": 16, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 11, @@ -40,9 +40,9 @@ } }, { + "type": "Text", "start": 16, "end": 17, - "type": "Text", "raw": "!", "data": "!" } diff --git a/packages/svelte/tests/parser-legacy/samples/element-with-text/output.json b/packages/svelte/tests/parser-legacy/samples/element-with-text/output.json index 0106e75b4..fde57c470 100644 --- a/packages/svelte/tests/parser-legacy/samples/element-with-text/output.json +++ b/packages/svelte/tests/parser-legacy/samples/element-with-text/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 0, "end": 17, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 17, - "type": "Element", "name": "span", "attributes": [], "children": [ { + "type": "Text", "start": 6, "end": 10, - "type": "Text", "raw": "test", "data": "test" } diff --git a/packages/svelte/tests/parser-legacy/samples/elements/output.json b/packages/svelte/tests/parser-legacy/samples/elements/output.json index c530d4376..6b51383d9 100644 --- a/packages/svelte/tests/parser-legacy/samples/elements/output.json +++ b/packages/svelte/tests/parser-legacy/samples/elements/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 15, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 15, - "type": "Element", "name": "!doctype", "attributes": [ { + "type": "Attribute", "start": 10, "end": 14, - "type": "Attribute", "name": "html", "value": true } diff --git a/packages/svelte/tests/parser-legacy/samples/event-handler/output.json b/packages/svelte/tests/parser-legacy/samples/event-handler/output.json index e19804f20..45b625667 100644 --- a/packages/svelte/tests/parser-legacy/samples/event-handler/output.json +++ b/packages/svelte/tests/parser-legacy/samples/event-handler/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 97, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 61, - "type": "Element", "name": "button", "attributes": [ { @@ -105,25 +105,25 @@ ], "children": [ { + "type": "Text", "start": 46, "end": 52, - "type": "Text", "raw": "toggle", "data": "toggle" } ] }, { + "type": "Text", "start": 61, "end": 63, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "IfBlock", "start": 63, "end": 97, - "type": "IfBlock", "expression": { "type": "Identifier", "start": 68, @@ -142,16 +142,16 @@ }, "children": [ { + "type": "Element", "start": 78, "end": 91, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 81, "end": 87, - "type": "Text", "raw": "hello!", "data": "hello!" } diff --git a/packages/svelte/tests/parser-legacy/samples/if-block-else/output.json b/packages/svelte/tests/parser-legacy/samples/if-block-else/output.json index 642768558..4ba9370d8 100644 --- a/packages/svelte/tests/parser-legacy/samples/if-block-else/output.json +++ b/packages/svelte/tests/parser-legacy/samples/if-block-else/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 51, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 51, - "type": "IfBlock", "expression": { "type": "Identifier", "start": 5, @@ -26,16 +26,16 @@ }, "children": [ { + "type": "Element", "start": 11, "end": 21, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 14, "end": 17, - "type": "Text", "raw": "foo", "data": "foo" } @@ -43,21 +43,21 @@ } ], "else": { + "type": "ElseBlock", "start": 29, "end": 46, - "type": "ElseBlock", "children": [ { + "type": "Element", "start": 31, "end": 45, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 34, "end": 41, - "type": "Text", "raw": "not foo", "data": "not foo" } diff --git a/packages/svelte/tests/parser-legacy/samples/if-block-elseif/output.json b/packages/svelte/tests/parser-legacy/samples/if-block-elseif/output.json index 2fdeddcf8..3e6953ab0 100644 --- a/packages/svelte/tests/parser-legacy/samples/if-block-elseif/output.json +++ b/packages/svelte/tests/parser-legacy/samples/if-block-elseif/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 89, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 89, - "type": "IfBlock", "expression": { "type": "BinaryExpression", "start": 5, @@ -59,16 +59,16 @@ }, "children": [ { + "type": "Element", "start": 14, "end": 41, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 17, "end": 37, - "type": "Text", "raw": "x is greater than 10", "data": "x is greater than 10" } @@ -76,15 +76,14 @@ } ], "else": { + "type": "ElseBlock", "start": 58, "end": 84, - "type": "ElseBlock", "children": [ { + "type": "IfBlock", "start": 58, "end": 89, - "type": "IfBlock", - "elseif": true, "expression": { "type": "BinaryExpression", "start": 52, @@ -136,22 +135,23 @@ }, "children": [ { + "type": "Element", "start": 60, "end": 83, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 63, "end": 79, - "type": "Text", "raw": "x is less than 5", "data": "x is less than 5" } ] } - ] + ], + "elseif": true } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/if-block/output.json b/packages/svelte/tests/parser-legacy/samples/if-block/output.json index 4c6fd9752..61ecf6e3b 100644 --- a/packages/svelte/tests/parser-legacy/samples/if-block/output.json +++ b/packages/svelte/tests/parser-legacy/samples/if-block/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 17, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 17, - "type": "IfBlock", "expression": { "type": "Identifier", "start": 5, @@ -26,9 +26,9 @@ }, "children": [ { + "type": "Text", "start": 9, "end": 12, - "type": "Text", "raw": "bar", "data": "bar" } diff --git a/packages/svelte/tests/parser-legacy/samples/implicitly-closed-li/output.json b/packages/svelte/tests/parser-legacy/samples/implicitly-closed-li/output.json index d46de69dd..a56701894 100644 --- a/packages/svelte/tests/parser-legacy/samples/implicitly-closed-li/output.json +++ b/packages/svelte/tests/parser-legacy/samples/implicitly-closed-li/output.json @@ -1,66 +1,66 @@ { "html": { + "type": "Fragment", "start": 0, "end": 31, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 31, - "type": "Element", "name": "ul", "attributes": [], "children": [ { + "type": "Text", "start": 4, "end": 6, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 6, "end": 13, - "type": "Element", "name": "li", "attributes": [], "children": [ { + "type": "Text", "start": 10, "end": 13, - "type": "Text", "raw": "a\n\t", "data": "a\n\t" } ] }, { + "type": "Element", "start": 13, "end": 20, - "type": "Element", "name": "li", "attributes": [], "children": [ { + "type": "Text", "start": 17, "end": 20, - "type": "Text", "raw": "b\n\t", "data": "b\n\t" } ] }, { + "type": "Element", "start": 20, "end": 26, - "type": "Element", "name": "li", "attributes": [], "children": [ { + "type": "Text", "start": 24, "end": 26, - "type": "Text", "raw": "c\n", "data": "c\n" } diff --git a/packages/svelte/tests/parser-legacy/samples/javascript-comments/output.json b/packages/svelte/tests/parser-legacy/samples/javascript-comments/output.json index 9ec95cfaa..951cf05ff 100644 --- a/packages/svelte/tests/parser-legacy/samples/javascript-comments/output.json +++ b/packages/svelte/tests/parser-legacy/samples/javascript-comments/output.json @@ -109,8 +109,7 @@ "type": "Block", "value": " another comment ", "start": 163, - "end": 184, - "has_trailing_newline": true + "end": 184 } ] } @@ -121,8 +120,7 @@ "type": "Line", "value": " comment", "start": 141, - "end": 151, - "has_trailing_newline": true + "end": 151 } ] } @@ -222,8 +220,7 @@ "type": "Line", "value": " a leading comment", "start": 10, - "end": 30, - "has_trailing_newline": true + "end": 30 } ], "trailingComments": [ @@ -290,8 +287,7 @@ "type": "Block", "value": "* a comment ", "start": 72, - "end": 88, - "has_trailing_newline": true + "end": 88 } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/nbsp/output.json b/packages/svelte/tests/parser-legacy/samples/nbsp/output.json index ec231bbe7..7d2158955 100644 --- a/packages/svelte/tests/parser-legacy/samples/nbsp/output.json +++ b/packages/svelte/tests/parser-legacy/samples/nbsp/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 0, "end": 19, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 19, - "type": "Element", "name": "span", "attributes": [], "children": [ { + "type": "Text", "start": 6, "end": 12, - "type": "Text", "raw": " ", "data": " " } diff --git a/packages/svelte/tests/parser-legacy/samples/no-error-if-before-closing/output.json b/packages/svelte/tests/parser-legacy/samples/no-error-if-before-closing/output.json index 4350d3cb5..79a26426d 100644 --- a/packages/svelte/tests/parser-legacy/samples/no-error-if-before-closing/output.json +++ b/packages/svelte/tests/parser-legacy/samples/no-error-if-before-closing/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 148, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 33, - "type": "IfBlock", "expression": { "type": "Literal", "start": 5, @@ -27,32 +27,32 @@ }, "children": [ { + "type": "Element", "start": 12, "end": 19, - "type": "Element", "name": "input", "attributes": [], "children": [] } ], "else": { + "type": "ElseBlock", "start": 27, "end": 28, - "type": "ElseBlock", "children": [] } }, { + "type": "Text", "start": 33, "end": 35, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "IfBlock", "start": 35, "end": 65, - "type": "IfBlock", "expression": { "type": "Literal", "start": 40, @@ -72,32 +72,32 @@ }, "children": [ { + "type": "Element", "start": 47, "end": 51, - "type": "Element", "name": "br", "attributes": [], "children": [] } ], "else": { + "type": "ElseBlock", "start": 59, "end": 60, - "type": "ElseBlock", "children": [] } }, { + "type": "Text", "start": 65, "end": 67, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "AwaitBlock", "start": 67, "end": 108, - "type": "AwaitBlock", "expression": { "type": "Literal", "start": 75, @@ -123,29 +123,29 @@ }, "error": null, "pending": { + "type": "PendingBlock", "start": 80, "end": 90, - "type": "PendingBlock", "children": [ { + "type": "Text", "start": 80, "end": 82, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 82, "end": 89, - "type": "Element", "name": "input", "attributes": [], "children": [] }, { + "type": "Text", "start": 89, "end": 90, - "type": "Text", "raw": "\n", "data": "\n" } @@ -153,14 +153,14 @@ "skip": false }, "then": { + "type": "ThenBlock", "start": 90, "end": 100, - "type": "ThenBlock", "children": [ { + "type": "Text", "start": 99, "end": 100, - "type": "Text", "raw": "\n", "data": "\n" } @@ -168,24 +168,24 @@ "skip": false }, "catch": { + "type": "CatchBlock", "start": null, "end": null, - "type": "CatchBlock", "children": [], "skip": true } }, { + "type": "Text", "start": 108, "end": 110, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "AwaitBlock", "start": 110, "end": 148, - "type": "AwaitBlock", "expression": { "type": "Literal", "start": 118, @@ -211,29 +211,29 @@ }, "error": null, "pending": { + "type": "PendingBlock", "start": 123, "end": 130, - "type": "PendingBlock", "children": [ { + "type": "Text", "start": 123, "end": 125, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 125, "end": 129, - "type": "Element", "name": "br", "attributes": [], "children": [] }, { + "type": "Text", "start": 129, "end": 130, - "type": "Text", "raw": "\n", "data": "\n" } @@ -241,14 +241,14 @@ "skip": false }, "then": { + "type": "ThenBlock", "start": 130, "end": 140, - "type": "ThenBlock", "children": [ { + "type": "Text", "start": 139, "end": 140, - "type": "Text", "raw": "\n", "data": "\n" } @@ -256,9 +256,9 @@ "skip": false }, "catch": { + "type": "CatchBlock", "start": null, "end": null, - "type": "CatchBlock", "children": [], "skip": true } diff --git a/packages/svelte/tests/parser-legacy/samples/raw-mustaches/output.json b/packages/svelte/tests/parser-legacy/samples/raw-mustaches/output.json index 3c349b472..7db0bb7ec 100644 --- a/packages/svelte/tests/parser-legacy/samples/raw-mustaches/output.json +++ b/packages/svelte/tests/parser-legacy/samples/raw-mustaches/output.json @@ -1,27 +1,27 @@ { "html": { + "type": "Fragment", "start": 0, "end": 34, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 34, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 3, "end": 4, - "type": "Text", "raw": " ", "data": " " }, { + "type": "RawMustacheTag", "start": 4, "end": 16, - "type": "RawMustacheTag", "expression": { "type": "Identifier", "start": 11, @@ -40,16 +40,16 @@ } }, { + "type": "Text", "start": 16, "end": 17, - "type": "Text", "raw": " ", "data": " " }, { + "type": "RawMustacheTag", "start": 17, "end": 29, - "type": "RawMustacheTag", "expression": { "type": "Identifier", "start": 24, @@ -68,9 +68,9 @@ } }, { + "type": "Text", "start": 29, "end": 30, - "type": "Text", "raw": " ", "data": " " } diff --git a/packages/svelte/tests/parser-legacy/samples/refs/output.json b/packages/svelte/tests/parser-legacy/samples/refs/output.json index 75f5d2d52..e2bda741f 100644 --- a/packages/svelte/tests/parser-legacy/samples/refs/output.json +++ b/packages/svelte/tests/parser-legacy/samples/refs/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 30, "end": 63, - "type": "Fragment", "children": [ { + "type": "Text", "start": 28, "end": 30, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "Element", "start": 30, "end": 63, - "type": "Element", "name": "canvas", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json b/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json index af4ec56a6..e0b50e3b9 100644 --- a/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json +++ b/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json @@ -1,34 +1,34 @@ { "html": { + "type": "Fragment", "start": 79, "end": 101, - "type": "Fragment", "children": [ { + "type": "Text", "start": 77, "end": 79, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "Element", "start": 79, "end": 101, - "type": "Element", "name": "h1", "attributes": [], "children": [ { + "type": "Text", "start": 83, "end": 89, - "type": "Text", "raw": "Hello ", "data": "Hello " }, { + "type": "MustacheTag", "start": 89, "end": 95, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 90, @@ -47,9 +47,9 @@ } }, { + "type": "Text", "start": 95, "end": 96, - "type": "Text", "raw": "!", "data": "!" } diff --git a/packages/svelte/tests/parser-legacy/samples/script-context-module-unquoted/output.json b/packages/svelte/tests/parser-legacy/samples/script-context-module-unquoted/output.json index 072e10178..03a526f04 100644 --- a/packages/svelte/tests/parser-legacy/samples/script-context-module-unquoted/output.json +++ b/packages/svelte/tests/parser-legacy/samples/script-context-module-unquoted/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 62, "end": 60, - "type": "Fragment", "children": [ { + "type": "Text", "start": 60, "end": 62, - "type": "Text", "raw": "\n\n", "data": "\n\n" } diff --git a/packages/svelte/tests/parser-legacy/samples/script/output.json b/packages/svelte/tests/parser-legacy/samples/script/output.json index efa31bddb..d3d4abd6b 100644 --- a/packages/svelte/tests/parser-legacy/samples/script/output.json +++ b/packages/svelte/tests/parser-legacy/samples/script/output.json @@ -1,34 +1,34 @@ { "html": { + "type": "Fragment", "start": 41, "end": 63, - "type": "Fragment", "children": [ { + "type": "Text", "start": 39, "end": 41, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "Element", "start": 41, "end": 63, - "type": "Element", "name": "h1", "attributes": [], "children": [ { + "type": "Text", "start": 45, "end": 51, - "type": "Text", "raw": "Hello ", "data": "Hello " }, { + "type": "MustacheTag", "start": 51, "end": 57, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 52, @@ -47,9 +47,9 @@ } }, { + "type": "Text", "start": 57, "end": 58, - "type": "Text", "raw": "!", "data": "!" } diff --git a/packages/svelte/tests/parser-legacy/samples/self-closing-element/output.json b/packages/svelte/tests/parser-legacy/samples/self-closing-element/output.json index 34052615d..f0ba3a5c0 100644 --- a/packages/svelte/tests/parser-legacy/samples/self-closing-element/output.json +++ b/packages/svelte/tests/parser-legacy/samples/self-closing-element/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 6, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 6, - "type": "Element", "name": "div", "attributes": [], "children": [] diff --git a/packages/svelte/tests/parser-legacy/samples/self-reference/output.json b/packages/svelte/tests/parser-legacy/samples/self-reference/output.json index 77bffaaa3..34310fcce 100644 --- a/packages/svelte/tests/parser-legacy/samples/self-reference/output.json +++ b/packages/svelte/tests/parser-legacy/samples/self-reference/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 57, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 57, - "type": "IfBlock", "expression": { "type": "BinaryExpression", "start": 5, @@ -59,21 +59,21 @@ }, "children": [ { - "start": 17, - "end": 51, "type": "InlineComponent", "name": "svelte:self", + "start": 17, + "end": 51, "attributes": [ { + "type": "Attribute", "start": 30, "end": 49, - "type": "Attribute", "name": "depth", "value": [ { + "type": "MustacheTag", "start": 37, "end": 48, - "type": "MustacheTag", "expression": { "type": "BinaryExpression", "start": 38, diff --git a/packages/svelte/tests/parser-legacy/samples/slotted-element/output.json b/packages/svelte/tests/parser-legacy/samples/slotted-element/output.json index 7028a367f..90ded6810 100644 --- a/packages/svelte/tests/parser-legacy/samples/slotted-element/output.json +++ b/packages/svelte/tests/parser-legacy/samples/slotted-element/output.json @@ -1,26 +1,26 @@ { "html": { + "type": "Fragment", "start": 0, "end": 45, - "type": "Fragment", "children": [ { + "type": "InlineComponent", "start": 0, "end": 45, - "type": "InlineComponent", "name": "Component", "attributes": [], "children": [ { + "type": "Element", "start": 11, "end": 33, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 16, "end": 26, - "type": "Attribute", "name": "slot", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/space-between-mustaches/output.json b/packages/svelte/tests/parser-legacy/samples/space-between-mustaches/output.json index c552a84b0..bb6de0bea 100644 --- a/packages/svelte/tests/parser-legacy/samples/space-between-mustaches/output.json +++ b/packages/svelte/tests/parser-legacy/samples/space-between-mustaches/output.json @@ -1,27 +1,27 @@ { "html": { + "type": "Fragment", "start": 0, "end": 24, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 24, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 3, "end": 4, - "type": "Text", "raw": " ", "data": " " }, { + "type": "MustacheTag", "start": 4, "end": 7, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 5, @@ -40,16 +40,16 @@ } }, { + "type": "Text", "start": 7, "end": 8, - "type": "Text", "raw": " ", "data": " " }, { + "type": "MustacheTag", "start": 8, "end": 11, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 9, @@ -68,16 +68,16 @@ } }, { + "type": "Text", "start": 11, "end": 14, - "type": "Text", "raw": " : ", "data": " : " }, { + "type": "MustacheTag", "start": 14, "end": 17, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 15, @@ -96,9 +96,9 @@ } }, { + "type": "Text", "start": 17, "end": 20, - "type": "Text", "raw": " : ", "data": " : " } diff --git a/packages/svelte/tests/parser-legacy/samples/spread/output.json b/packages/svelte/tests/parser-legacy/samples/spread/output.json index e0a45f9d7..3b79aa967 100644 --- a/packages/svelte/tests/parser-legacy/samples/spread/output.json +++ b/packages/svelte/tests/parser-legacy/samples/spread/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 22, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 22, - "type": "Element", "name": "div", "attributes": [ { + "type": "Spread", "start": 5, "end": 15, - "type": "Spread", "expression": { "type": "Identifier", "start": 9, diff --git a/packages/svelte/tests/parser-legacy/samples/style-inside-head/output.json b/packages/svelte/tests/parser-legacy/samples/style-inside-head/output.json index 5082c5534..733f9e76f 100644 --- a/packages/svelte/tests/parser-legacy/samples/style-inside-head/output.json +++ b/packages/svelte/tests/parser-legacy/samples/style-inside-head/output.json @@ -1,27 +1,27 @@ { "html": { + "type": "Fragment", "start": 0, "end": 42, - "type": "Fragment", "children": [ { - "start": 0, - "end": 42, "type": "Head", "name": "svelte:head", + "start": 0, + "end": 42, "attributes": [], "children": [ { + "type": "Element", "start": 13, "end": 28, - "type": "Element", "name": "style", "attributes": [], "children": [ { + "type": "Text", "start": 20, "end": 20, - "type": "Text", "data": "" } ] diff --git a/packages/svelte/tests/parser-legacy/samples/textarea-children/output.json b/packages/svelte/tests/parser-legacy/samples/textarea-children/output.json index cdb4b885d..60477807b 100644 --- a/packages/svelte/tests/parser-legacy/samples/textarea-children/output.json +++ b/packages/svelte/tests/parser-legacy/samples/textarea-children/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 61, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 61, - "type": "Element", "name": "textarea", "attributes": [], "children": [ @@ -19,9 +19,9 @@ "data": "\n\t<p>not actually an element. " }, { + "type": "MustacheTag", "start": 40, "end": 45, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 41, diff --git a/packages/svelte/tests/parser-legacy/samples/textarea-end-tag/output.json b/packages/svelte/tests/parser-legacy/samples/textarea-end-tag/output.json index 9693db272..ed3c85b09 100644 --- a/packages/svelte/tests/parser-legacy/samples/textarea-end-tag/output.json +++ b/packages/svelte/tests/parser-legacy/samples/textarea-end-tag/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 117, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 117, - "type": "Element", "name": "textarea", "attributes": [], "children": [ @@ -19,9 +19,9 @@ "data": "\n\t<p>not actu </textar ally an element. " }, { + "type": "MustacheTag", "start": 50, "end": 55, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 51, diff --git a/packages/svelte/tests/parser-legacy/samples/transition-intro-no-params/output.json b/packages/svelte/tests/parser-legacy/samples/transition-intro-no-params/output.json index 3a025dc62..f30788d75 100644 --- a/packages/svelte/tests/parser-legacy/samples/transition-intro-no-params/output.json +++ b/packages/svelte/tests/parser-legacy/samples/transition-intro-no-params/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 27, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 27, - "type": "Element", "name": "div", "attributes": [ { @@ -23,9 +23,9 @@ ], "children": [ { + "type": "Text", "start": 13, "end": 21, - "type": "Text", "raw": "fades in", "data": "fades in" } diff --git a/packages/svelte/tests/parser-legacy/samples/transition-intro/output.json b/packages/svelte/tests/parser-legacy/samples/transition-intro/output.json index 746aa08b8..ae52f72c5 100644 --- a/packages/svelte/tests/parser-legacy/samples/transition-intro/output.json +++ b/packages/svelte/tests/parser-legacy/samples/transition-intro/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 45, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 45, - "type": "Element", "name": "div", "attributes": [ { @@ -91,9 +91,9 @@ ], "children": [ { + "type": "Text", "start": 31, "end": 39, - "type": "Text", "raw": "fades in", "data": "fades in" } diff --git a/packages/svelte/tests/parser-legacy/samples/unusual-identifier/output.json b/packages/svelte/tests/parser-legacy/samples/unusual-identifier/output.json index 86ad57871..96e6c0a13 100644 --- a/packages/svelte/tests/parser-legacy/samples/unusual-identifier/output.json +++ b/packages/svelte/tests/parser-legacy/samples/unusual-identifier/output.json @@ -1,41 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 41, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 41, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 13, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 13 - } - }, - "name": "things" - }, "children": [ { + "type": "Element", "start": 22, "end": 33, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 25, "end": 29, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 26, @@ -61,6 +45,22 @@ "name": "𐊧", "start": 17, "end": 19 + }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 13 + } + }, + "name": "things" } } ] diff --git a/packages/svelte/tests/parser-legacy/samples/whitespace-after-script-tag/output.json b/packages/svelte/tests/parser-legacy/samples/whitespace-after-script-tag/output.json index 4af82ca4b..69b238244 100644 --- a/packages/svelte/tests/parser-legacy/samples/whitespace-after-script-tag/output.json +++ b/packages/svelte/tests/parser-legacy/samples/whitespace-after-script-tag/output.json @@ -1,34 +1,34 @@ { "html": { + "type": "Fragment", "start": 51, "end": 73, - "type": "Fragment", "children": [ { + "type": "Text", "start": 49, "end": 51, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "Element", "start": 51, "end": 73, - "type": "Element", "name": "h1", "attributes": [], "children": [ { + "type": "Text", "start": 55, "end": 61, - "type": "Text", "raw": "Hello ", "data": "Hello " }, { + "type": "MustacheTag", "start": 61, "end": 67, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 62, @@ -47,9 +47,9 @@ } }, { + "type": "Text", "start": 67, "end": 68, - "type": "Text", "raw": "!", "data": "!" } diff --git a/packages/svelte/tests/parser-legacy/samples/whitespace-after-style-tag/output.json b/packages/svelte/tests/parser-legacy/samples/whitespace-after-style-tag/output.json index 201c4ad02..51053485f 100644 --- a/packages/svelte/tests/parser-legacy/samples/whitespace-after-style-tag/output.json +++ b/packages/svelte/tests/parser-legacy/samples/whitespace-after-style-tag/output.json @@ -1,29 +1,29 @@ { "html": { + "type": "Fragment", "start": 0, "end": 14, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 14, - "type": "Element", "name": "div", "attributes": [], "children": [ { + "type": "Text", "start": 5, "end": 8, - "type": "Text", "raw": "foo", "data": "foo" } ] }, { + "type": "Text", "start": 14, "end": 16, - "type": "Text", "raw": "\n\n", "data": "\n\n" } @@ -39,9 +39,13 @@ "type": "Rule", "prelude": { "type": "SelectorList", + "start": 25, + "end": 28, "children": [ { "type": "Selector", + "start": 25, + "end": 28, "children": [ { "type": "TypeSelector", @@ -49,27 +53,23 @@ "start": 25, "end": 28 } - ], - "start": 25, - "end": 28 + ] } - ], - "start": 25, - "end": 28 + ] }, "block": { "type": "Block", + "start": 29, + "end": 47, "children": [ { "type": "Declaration", - "property": "color", - "value": "red", "start": 33, - "end": 43 + "end": 43, + "property": "color", + "value": "red" } - ], - "start": 29, - "end": 47 + ] }, "start": 25, "end": 47 diff --git a/packages/svelte/tests/parser-legacy/samples/whitespace-leading-trailing/output.json b/packages/svelte/tests/parser-legacy/samples/whitespace-leading-trailing/output.json index b8501774e..baffebfed 100644 --- a/packages/svelte/tests/parser-legacy/samples/whitespace-leading-trailing/output.json +++ b/packages/svelte/tests/parser-legacy/samples/whitespace-leading-trailing/output.json @@ -1,27 +1,27 @@ { "html": { + "type": "Fragment", "start": 6, "end": 36, - "type": "Fragment", "children": [ { + "type": "Text", "start": 0, "end": 6, - "type": "Text", "raw": "\n\n\t\t\t\t", "data": "\n\n\t\t\t\t" }, { + "type": "Element", "start": 6, "end": 36, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 9, "end": 32, - "type": "Text", "raw": "just chillin' over here", "data": "just chillin' over here" } diff --git a/packages/svelte/tests/parser-legacy/samples/whitespace-normal/output.json b/packages/svelte/tests/parser-legacy/samples/whitespace-normal/output.json index 6d7904b50..4fb88b818 100644 --- a/packages/svelte/tests/parser-legacy/samples/whitespace-normal/output.json +++ b/packages/svelte/tests/parser-legacy/samples/whitespace-normal/output.json @@ -1,34 +1,34 @@ { "html": { + "type": "Fragment", "start": 0, "end": 65, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 65, - "type": "Element", "name": "h1", "attributes": [], "children": [ { + "type": "Text", "start": 4, "end": 10, - "type": "Text", "raw": "Hello ", "data": "Hello " }, { + "type": "Element", "start": 10, "end": 35, - "type": "Element", "name": "strong", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 18, "end": 24, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 19, @@ -47,25 +47,25 @@ } }, { + "type": "Text", "start": 24, "end": 26, - "type": "Text", "raw": "! ", "data": "! " } ] }, { + "type": "Element", "start": 35, "end": 60, - "type": "Element", "name": "span", "attributes": [], "children": [ { + "type": "Text", "start": 41, "end": 53, - "type": "Text", "raw": "How are you?", "data": "How are you?" } diff --git a/packages/svelte/tests/parser-modern/samples/css-nth-syntax/input.svelte b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/input.svelte new file mode 100644 index 000000000..b8e2d4086 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/input.svelte @@ -0,0 +1,26 @@ +<style> + /* test that all these are parsed correctly */ + h1:nth-of-type(2n+1){ + background: red; + } + h1:nth-child(-n + 3 of li.important) { + background: red; + } + h1:nth-child(1) { + background: red; + } + h1:nth-child(p) { + background: red; + } + h1:nth-child(n+7) { + background: red; + } + h1:nth-child(even) { + background: red; + } + h1:nth-child(odd) { + background: red; + } +</style> + +<h1>Broken</h1> diff --git a/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json new file mode 100644 index 000000000..1cea48b17 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json @@ -0,0 +1,520 @@ +{ + "css": { + "type": "Style", + "start": 0, + "end": 467, + "attributes": [], + "children": [ + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 60, + "end": 80, + "children": [ + { + "type": "Selector", + "start": 60, + "end": 80, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 60, + "end": 62 + }, + { + "type": "PseudoClassSelector", + "name": "nth-of-type", + "args": { + "type": "SelectorList", + "start": 75, + "end": 79, + "children": [ + { + "type": "Selector", + "start": 75, + "end": 79, + "children": [ + { + "type": "Nth", + "value": "2n+1", + "start": 75, + "end": 79 + } + ] + } + ] + }, + "start": 62, + "end": 80 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 80, + "end": 112, + "children": [ + { + "type": "Declaration", + "start": 90, + "end": 105, + "property": "background", + "value": "red" + } + ] + }, + "start": 60, + "end": 112 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 117, + "end": 153, + "children": [ + { + "type": "Selector", + "start": 117, + "end": 153, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 117, + "end": 119 + }, + { + "type": "PseudoClassSelector", + "name": "nth-child", + "args": { + "type": "SelectorList", + "start": 130, + "end": 152, + "children": [ + { + "type": "Selector", + "start": 130, + "end": 152, + "children": [ + { + "type": "Nth", + "value": "-n + 3 of ", + "start": 130, + "end": 140 + }, + { + "type": "TypeSelector", + "name": "li", + "start": 140, + "end": 142 + }, + { + "type": "ClassSelector", + "name": "important", + "start": 142, + "end": 152 + } + ] + } + ] + }, + "start": 119, + "end": 153 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 154, + "end": 186, + "children": [ + { + "type": "Declaration", + "start": 164, + "end": 179, + "property": "background", + "value": "red" + } + ] + }, + "start": 117, + "end": 186 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 191, + "end": 206, + "children": [ + { + "type": "Selector", + "start": 191, + "end": 206, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 191, + "end": 193 + }, + { + "type": "PseudoClassSelector", + "name": "nth-child", + "args": { + "type": "SelectorList", + "start": 204, + "end": 205, + "children": [ + { + "type": "Selector", + "start": 204, + "end": 205, + "children": [ + { + "type": "Nth", + "value": "1", + "start": 204, + "end": 205 + } + ] + } + ] + }, + "start": 193, + "end": 206 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 207, + "end": 239, + "children": [ + { + "type": "Declaration", + "start": 217, + "end": 232, + "property": "background", + "value": "red" + } + ] + }, + "start": 191, + "end": 239 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 244, + "end": 259, + "children": [ + { + "type": "Selector", + "start": 244, + "end": 259, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 244, + "end": 246 + }, + { + "type": "PseudoClassSelector", + "name": "nth-child", + "args": { + "type": "SelectorList", + "start": 257, + "end": 258, + "children": [ + { + "type": "Selector", + "start": 257, + "end": 258, + "children": [ + { + "type": "TypeSelector", + "name": "p", + "start": 257, + "end": 258 + } + ] + } + ] + }, + "start": 246, + "end": 259 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 260, + "end": 292, + "children": [ + { + "type": "Declaration", + "start": 270, + "end": 285, + "property": "background", + "value": "red" + } + ] + }, + "start": 244, + "end": 292 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 297, + "end": 314, + "children": [ + { + "type": "Selector", + "start": 297, + "end": 314, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 297, + "end": 299 + }, + { + "type": "PseudoClassSelector", + "name": "nth-child", + "args": { + "type": "SelectorList", + "start": 310, + "end": 313, + "children": [ + { + "type": "Selector", + "start": 310, + "end": 313, + "children": [ + { + "type": "Nth", + "value": "n+7", + "start": 310, + "end": 313 + } + ] + } + ] + }, + "start": 299, + "end": 314 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 315, + "end": 347, + "children": [ + { + "type": "Declaration", + "start": 325, + "end": 340, + "property": "background", + "value": "red" + } + ] + }, + "start": 297, + "end": 347 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 352, + "end": 370, + "children": [ + { + "type": "Selector", + "start": 352, + "end": 370, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 352, + "end": 354 + }, + { + "type": "PseudoClassSelector", + "name": "nth-child", + "args": { + "type": "SelectorList", + "start": 365, + "end": 369, + "children": [ + { + "type": "Selector", + "start": 365, + "end": 369, + "children": [ + { + "type": "Nth", + "value": "even", + "start": 365, + "end": 369 + } + ] + } + ] + }, + "start": 354, + "end": 370 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 371, + "end": 403, + "children": [ + { + "type": "Declaration", + "start": 381, + "end": 396, + "property": "background", + "value": "red" + } + ] + }, + "start": 352, + "end": 403 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 408, + "end": 425, + "children": [ + { + "type": "Selector", + "start": 408, + "end": 425, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 408, + "end": 410 + }, + { + "type": "PseudoClassSelector", + "name": "nth-child", + "args": { + "type": "SelectorList", + "start": 421, + "end": 424, + "children": [ + { + "type": "Selector", + "start": 421, + "end": 424, + "children": [ + { + "type": "Nth", + "value": "odd", + "start": 421, + "end": 424 + } + ] + } + ] + }, + "start": 410, + "end": 425 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 426, + "end": 458, + "children": [ + { + "type": "Declaration", + "start": 436, + "end": 451, + "property": "background", + "value": "red" + } + ] + }, + "start": 408, + "end": 458 + } + ], + "content": { + "start": 7, + "end": 459, + "styles": "\n /* test that all these are parsed correctly */\n\th1:nth-of-type(2n+1){\n background: red;\n }\n h1:nth-child(-n + 3 of li.important) {\n background: red;\n }\n h1:nth-child(1) {\n background: red;\n }\n h1:nth-child(p) {\n background: red;\n }\n h1:nth-child(n+7) {\n background: red;\n }\n h1:nth-child(even) {\n background: red;\n }\n h1:nth-child(odd) {\n background: red;\n }\n" + } + }, + "js": [], + "start": 469, + "end": 484, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 467, + "end": 469, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "RegularElement", + "start": 469, + "end": 484, + "name": "h1", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 473, + "end": 479, + "raw": "Broken", + "data": "Broken" + } + ], + "transparent": true + } + } + ], + "transparent": false + }, + "options": null +} diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/set-property-before-mounted/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/set-property-before-mounted/_config.js new file mode 100644 index 000000000..843e16452 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/set-property-before-mounted/_config.js @@ -0,0 +1,25 @@ +import { test } from '../../assert'; + +const tick = () => Promise.resolve(); + +export default test({ + async test({ assert, target, componentCtor }) { + target.innerHTML = '<custom-element red white></custom-element>'; + const ce = target.querySelector('custom-element'); + ce.prop = 1; + customElements.define('custom-element', componentCtor.element); + await tick(); + await tick(); + + const ce_root = target.querySelector('custom-element').shadowRoot; + const p = ce_root.querySelector('p'); + + assert.equal(p.textContent, '1'); + + ce.prop = 2; + await tick(); + await tick(); + + assert.equal(p.textContent, '2'); + } +}); diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/set-property-before-mounted/main.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/set-property-before-mounted/main.svelte new file mode 100644 index 000000000..0be0f59d9 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/set-property-before-mounted/main.svelte @@ -0,0 +1,5 @@ +<script> + export let prop; +</script> + +<p>{prop}</p> diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-input-checkbox-deep-contextual/main.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-input-checkbox-deep-contextual/main.svelte index 1d51c2b14..e092430b2 100644 --- a/packages/svelte/tests/runtime-legacy/samples/binding-input-checkbox-deep-contextual/main.svelte +++ b/packages/svelte/tests/runtime-legacy/samples/binding-input-checkbox-deep-contextual/main.svelte @@ -11,4 +11,4 @@ <div><input type='checkbox' bind:checked={item.completed}><p>{item.description}</p></div> {/each} -<p>{numCompleted} completed</p> \ No newline at end of file +<p>{numCompleted} completed</p> diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-this-multiple/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-this-multiple/_config.js new file mode 100644 index 000000000..07356ad6f --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-this-multiple/_config.js @@ -0,0 +1,25 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + test({ assert, component, target }) { + const [b1, b2, b3] = target.querySelectorAll('button'); + const first_h1 = target.querySelector('h1'); + + assert.deepEqual(component.log, [undefined, first_h1]); + + flushSync(() => { + b3.click(); + }); + + const third_h1 = target.querySelector('h1'); + + assert.deepEqual(component.log, [undefined, first_h1, third_h1]); + + flushSync(() => { + b1.click(); + }); + + assert.deepEqual(component.log, [undefined, first_h1, third_h1, target.querySelector('h1')]); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-this-multiple/main.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-this-multiple/main.svelte new file mode 100644 index 000000000..bde2158c5 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-this-multiple/main.svelte @@ -0,0 +1,27 @@ +<script> + let activeTab = 0; + let activeHeading; + export let log = []; + + $: log.push(activeHeading); +</script> + +<div class="tabs"> + <div class="tab-toggles"> + <button class:active={activeTab === 0} on:click={() => activeTab = 0}>Tab 1</button> + <button class:active={activeTab === 1} on:click={() => activeTab = 1}>Tab 2</button> + <button class:active={activeTab === 2} on:click={() => activeTab = 2}>Tab 3</button> + </div> + <div class="tab-content"> + {#if activeTab === 0} + <div><h1 bind:this={activeHeading}>Tab 1</h1></div> + {/if} + {#if activeTab === 1} + <div><h1 bind:this={activeHeading}>Tab 2</h1></div> + {/if} + {#if activeTab === 2} + <div><h1 bind:this={activeHeading}>Tab 3</h1></div> + {/if} + </div> + <duiv> +</div> diff --git a/packages/svelte/tests/runtime-legacy/samples/event-handler-undefined/_config.js b/packages/svelte/tests/runtime-legacy/samples/event-handler-undefined/_config.js new file mode 100644 index 000000000..662164af4 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/event-handler-undefined/_config.js @@ -0,0 +1,14 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target }) { + const input = target.querySelector('input'); + + flushSync(() => { + input?.click(); + }); + + assert.htmlEqual(target.innerHTML, `<input>`); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/event-handler-undefined/main.svelte b/packages/svelte/tests/runtime-legacy/samples/event-handler-undefined/main.svelte new file mode 100644 index 000000000..770c17901 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/event-handler-undefined/main.svelte @@ -0,0 +1 @@ +<input on:click /> diff --git a/packages/svelte/tests/validator/samples/security-anchor-rel-noreferer-legacy/_config.js b/packages/svelte/tests/runtime-legacy/samples/keyed-each-index-same-2/_config.js similarity index 53% rename from packages/svelte/tests/validator/samples/security-anchor-rel-noreferer-legacy/_config.js rename to packages/svelte/tests/runtime-legacy/samples/keyed-each-index-same-2/_config.js index 6e402c327..8627d28b2 100644 --- a/packages/svelte/tests/validator/samples/security-anchor-rel-noreferer-legacy/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/keyed-each-index-same-2/_config.js @@ -1,8 +1,5 @@ import { test } from '../../test'; export default test({ - skip: true, - compileOptions: { - // legacy: true - } + html: '<div>0</div><div>1</div>' }); diff --git a/packages/svelte/tests/runtime-legacy/samples/keyed-each-index-same-2/main.svelte b/packages/svelte/tests/runtime-legacy/samples/keyed-each-index-same-2/main.svelte new file mode 100644 index 000000000..c21812be4 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/keyed-each-index-same-2/main.svelte @@ -0,0 +1,3 @@ +{#each ["a", "b"] as result, i (i)} + <div>{i}</div> +{/each} diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index da596dd7a..a1944d47e 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -63,6 +63,7 @@ export interface RuntimeTest<Props extends Record<string, any> = Record<string, intro?: boolean; load_compiled?: boolean; error?: string; + runtime_error?: string; warnings?: string[]; expect_unhandled_rejections?: boolean; withoutNormalizeHtml?: boolean; @@ -172,6 +173,11 @@ async function run_test_variant( window.document.head.innerHTML = styles ? `<style>${styles}</style>` : ''; window.document.body.innerHTML = '<main></main>'; + window.addEventListener('error', (e) => { + unhandled_rejection = e.error; + e.preventDefault(); + }); + let mod = await import(`${cwd}/_output/client/main.svelte.js`); const target = window.document.querySelector('main') as HTMLElement; @@ -315,7 +321,9 @@ async function run_test_variant( } } } catch (err) { - if (config.error && !unintended_error) { + if (config.runtime_error) { + assert.equal((err as Error).message, config.runtime_error); + } else if (config.error && !unintended_error) { assert.equal((err as Error).message, config.error); } else { throw err; diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-casing/_config.js b/packages/svelte/tests/runtime-runes/samples/attribute-spread-casing/_config.js new file mode 100644 index 000000000..eac7b7291 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-spread-casing/_config.js @@ -0,0 +1,29 @@ +import { test } from '../../test'; + +export default test({ + // There's a slight difference in the output between modes, because the server doesn't know + // whether or not the custom element has the readonly boolean, so it plays it save and + // assumes it does. + html: ` + <button>click me</button> + <input> + <input> + + <custom-element readonly="false"></custom-element> + <custom-element readonly="false"></custom-element> + + <svg readonly="false"></svg> + <svg readonly="false"></svg> + `, + ssrHtml: ` + <button>click me</button> + <input> + <input> + + <custom-element></custom-element> + <custom-element readonly="false"></custom-element> + + <svg readonly="false"></svg> + <svg readonly="false"></svg> +` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/attribute-spread-casing/main.svelte b/packages/svelte/tests/runtime-runes/samples/attribute-spread-casing/main.svelte new file mode 100644 index 000000000..88cb1cfee --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/attribute-spread-casing/main.svelte @@ -0,0 +1,18 @@ +<script> + const disabled = { dIsAbLeD: false }; + const readonly = { readonly: false } + const readOnly = { readOnly: false } +</script> + +<!-- lowercase, then compare --> +<button {...disabled}>click me</button> +<input {...readonly}> +<input {...readOnly}> + +<!-- keep casing --> +<custom-element {...readonly}></custom-element> +<custom-element {...readOnly}></custom-element> + +<!-- keep casing --> +<svg {...readonly}></svg> +<svg {...readOnly}></svg> diff --git a/packages/svelte/tests/runtime-runes/samples/directives-with-member-access/main.svelte b/packages/svelte/tests/runtime-runes/samples/directives-with-member-access/main.svelte index 66eb1eed3..c5e708799 100644 --- a/packages/svelte/tests/runtime-runes/samples/directives-with-member-access/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/directives-with-member-access/main.svelte @@ -14,6 +14,8 @@ in other words a number. Relatedly, people should not do this. It is stupid. --> <div use:directive.b.c-d /> <div transition:directive.b.c-d /> -<div animate:directive.b.c-d /> +{#each [] as i (i)} + <div animate:directive.b.c-d /> +{/each} <div in:directive.b.c-d /> <div out:directive.b.c-d /> diff --git a/packages/svelte/tests/runtime-runes/samples/each-bind-this-member/_config.js b/packages/svelte/tests/runtime-runes/samples/each-bind-this-member/_config.js new file mode 100644 index 000000000..4b5596cb3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-bind-this-member/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target, component }) { + assert.equal(target.querySelector('img'), component.items[0].img); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/each-bind-this-member/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-bind-this-member/main.svelte new file mode 100644 index 000000000..a685cc9c8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/each-bind-this-member/main.svelte @@ -0,0 +1,11 @@ +<script> + let { items = [{ src: 'https://ds' }] } = $props(); +</script> + +{#each items as item, i} + <img + src={item.src} + bind:this={items[i].img} + alt="slider{i}" + /> +{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/each-mutation/_config.js b/packages/svelte/tests/runtime-runes/samples/each-mutation/_config.js index 399f29a43..0dac8c623 100644 --- a/packages/svelte/tests/runtime-runes/samples/each-mutation/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/each-mutation/_config.js @@ -4,7 +4,7 @@ export default test({ html: ` <button>foo</button> <button>foo</button> - <button>foo</button> + <button>FOO</button> `, async test({ assert, target }) { @@ -25,7 +25,7 @@ export default test({ ` <button>bar</button> <button>bar</button> - <button>foo</button> + <button>BAR</button> ` ); } diff --git a/packages/svelte/tests/runtime-runes/samples/each-mutation/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-mutation/main.svelte index 5bf92b274..6e0223d93 100644 --- a/packages/svelte/tests/runtime-runes/samples/each-mutation/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/each-mutation/main.svelte @@ -1,20 +1,20 @@ <script> - let works1 = $state([{ text: 'foo' }]); + let a = $state([{ text: 'foo' }]); + + let b = $state([{ text: 'foo' }]); let text = $state('foo'); - let works2 = $state([{ get text() { return text }, set text(v) { text = v }}]); - - let doesntwork = $state([{ text: 'foo' }]); + let c = $state([{ get text() { return text.toUpperCase() }, set text(v) { text = v }}]); </script> -{#each works1 as item, i} - <button on:click={() => works1[i].text = 'bar'}>{item.text}</button> +{#each a as item, i} + <button on:click={() => a[i].text = 'bar'}>{item.text}</button> {/each} -{#each works2 as item} +{#each b as item} <button on:click={() => item.text = 'bar'}>{item.text}</button> {/each} -{#each doesntwork as item} +{#each c as item} <button on:click={() => item.text = 'bar'}>{item.text}</button> {/each} diff --git a/packages/svelte/tests/runtime-runes/samples/effect-active/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-active/_config.js new file mode 100644 index 000000000..69ba5793b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-active/_config.js @@ -0,0 +1,15 @@ +import { test } from '../../test'; + +export default test({ + ssrHtml: ` + <p>false</p> + <p>false</p> + <p>false</p> + `, + + html: ` + <p>false</p> + <p>true</p> + <p>true</p> + ` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-active/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-active/main.svelte new file mode 100644 index 000000000..a65a355ee --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-active/main.svelte @@ -0,0 +1,11 @@ +<script> + const foo = $effect.active(); + let bar = $state(false); + $effect.pre(() => { + bar = $effect.active(); + }); +</script> + +<p>{foo}</p> +<p>{bar}</p> +<p>{$effect.active()}</p> diff --git a/packages/svelte/tests/runtime-runes/samples/effect-infinite/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-infinite/_config.js new file mode 100644 index 000000000..763c5f58f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-infinite/_config.js @@ -0,0 +1,7 @@ +import { test } from '../../test'; + +export default test({ + runtime_error: + 'ERR_SVELTE_TOO_MANY_UPDATES: Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops.', + async test({ assert, target }) {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-infinite/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-infinite/main.svelte new file mode 100644 index 000000000..444467d99 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-infinite/main.svelte @@ -0,0 +1,11 @@ +<script> + const v = { value: 1 }; + let s = $state(v) + + $effect(() => { + s = v; + s; + }); +</script> + +{JSON.stringify(s)} diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order/main.svelte index b83c73b95..6f1967242 100644 --- a/packages/svelte/tests/runtime-runes/samples/effect-order/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/effect-order/main.svelte @@ -1,21 +1,20 @@ <script> const {log} = $props(); - let myList = $state([{ name: 'one' ,age: 4 }, {name: 'two', age: 5}]); - - let myDerived = $derived(myList) + let s = $state(0); + let d = $derived(s) $effect(() => { - myList + s; log.push('A') }) $effect(() => { - myDerived + d; log.push('B') }) </script> -<h1>Hello {myList[0].age}</h1> +<h1>{s}</h1> -<button on:click={() => myList[0].age++ }>Click</button> +<button on:click={() =>s++ }>Click</button> diff --git a/packages/svelte/tests/runtime-runes/samples/effect-root/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-root/_config.js new file mode 100644 index 000000000..b5e2a1a80 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-root/_config.js @@ -0,0 +1,32 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + get props() { + return { log: [] }; + }, + + async test({ assert, target, component }) { + const [b1, b2, b3] = target.querySelectorAll('button'); + + flushSync(() => { + b1.click(); + b2.click(); + }); + + assert.deepEqual(component.log, [0, 1]); + + flushSync(() => { + b3.click(); + }); + + assert.deepEqual(component.log, [0, 1, 'cleanup 1', 'cleanup 2']); + + flushSync(() => { + b1.click(); + b2.click(); + }); + + assert.deepEqual(component.log, [0, 1, 'cleanup 1', 'cleanup 2']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/effect-root/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-root/main.svelte new file mode 100644 index 000000000..d646bea2c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/effect-root/main.svelte @@ -0,0 +1,27 @@ +<script> + let { log} = $props(); + + let x = $state(0); + let y = $state(0); + + const cleanup = $effect.root(() => { + $effect(() => { + log.push(x); + }); + + const nested_cleanup = $effect.root(() => { + return () => { + log.push('cleanup 2') ; + } + }); + + return () => { + log.push('cleanup 1'); + nested_cleanup(); + } + }); +</script> + +<button on:click={() => x++}>{x}</button> +<button on:click={() => y++}>{y}</button> +<button on:click={() => cleanup()}>cleanup</button> diff --git a/packages/svelte/tests/runtime-runes/samples/event-arguments-2/_config.js b/packages/svelte/tests/runtime-runes/samples/event-arguments-2/_config.js new file mode 100644 index 000000000..f2e6c67f8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-arguments-2/_config.js @@ -0,0 +1,13 @@ +import { test } from '../../test'; + +export default test({ + html: `<button>0</button>`, + + async test({ assert, target }) { + const [b1] = target.querySelectorAll('button'); + + b1?.click(); + await Promise.resolve(); + assert.htmlEqual(target.innerHTML, '<button>1</button>'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-arguments-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-arguments-2/main.svelte new file mode 100644 index 000000000..39333849f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-arguments-2/main.svelte @@ -0,0 +1,8 @@ +<script> + let count = $state(0); + function increment(...args) { + count += args.length; + } +</script> + +<button on:click={increment}>{count}</button> diff --git a/packages/svelte/tests/runtime-runes/samples/event-arguments/_config.js b/packages/svelte/tests/runtime-runes/samples/event-arguments/_config.js new file mode 100644 index 000000000..f2e6c67f8 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-arguments/_config.js @@ -0,0 +1,13 @@ +import { test } from '../../test'; + +export default test({ + html: `<button>0</button>`, + + async test({ assert, target }) { + const [b1] = target.querySelectorAll('button'); + + b1?.click(); + await Promise.resolve(); + assert.htmlEqual(target.innerHTML, '<button>1</button>'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-arguments/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-arguments/main.svelte new file mode 100644 index 000000000..d6569e26f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-arguments/main.svelte @@ -0,0 +1,8 @@ +<script> + let count = $state(0); + function increment() { + count += arguments.length; + } +</script> + +<button on:click={increment}>{count}</button> diff --git a/packages/svelte/tests/runtime-runes/samples/event-attribute-template/_config.js b/packages/svelte/tests/runtime-runes/samples/event-attribute-template/_config.js new file mode 100644 index 000000000..340b0ff4c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-attribute-template/_config.js @@ -0,0 +1,15 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, component }) { + const [b1] = target.querySelectorAll('button'); + + flushSync(() => { + b1?.click(); + }); + + await Promise.resolve(); + assert.deepEqual(component.log, ['onclick']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-attribute-template/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-attribute-template/main.svelte new file mode 100644 index 000000000..66db0b601 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-attribute-template/main.svelte @@ -0,0 +1,10 @@ +<script> + const {log = []} = $props(); + + function send() { + log.push("onclick") + } +</script> + +{undefined}<hr/> +<button onclick={send}>Send Event</button> diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-derived/_config.js new file mode 100644 index 000000000..ea62c919a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-derived/_config.js @@ -0,0 +1,31 @@ +import { test } from '../../test'; + +/** + * @type {any[]} + */ +let log; + +export default test({ + compileOptions: { + dev: true + }, + + get props() { + log = []; + return { + push: (/** @type {any} */ ...v) => log.push(...v) + }; + }, + + async test({ assert, target }) { + const button = target.querySelector('button'); + + button?.click(); + await Promise.resolve(); + + button?.click(); + await Promise.resolve(); + + assert.deepEqual(log, ['X', 'init', 'XX', 'update', 'XXX', 'update']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-derived/main.svelte new file mode 100644 index 000000000..db4968aae --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-derived/main.svelte @@ -0,0 +1,11 @@ +<script> + /** @type {{ push: (v: any) => void }} */ + let { push } = $props(); + + let x = $state('x'); + let y = $derived(x.toUpperCase()); + + $inspect(y, push); +</script> + +<button on:click={() => x += 'x'}>{x}</button> diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js new file mode 100644 index 000000000..943b82989 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js @@ -0,0 +1,39 @@ +import { test } from '../../test'; + +/** + * @type {any[]} + */ +let log; +/** + * @type {typeof console.log}} + */ +let original_log; + +export default test({ + compileOptions: { + dev: true + }, + before_test() { + log = []; + original_log = console.log; + console.log = (...v) => { + log.push(...v); + }; + }, + after_test() { + console.log = original_log; + }, + async test({ assert, target }) { + assert.deepEqual(log, []); + + const [b1, b2] = target.querySelectorAll('button'); + b1.click(); + b2.click(); + await Promise.resolve(); + + assert.ok( + log[0].stack.startsWith('Error:') && log[0].stack.includes('HTMLButtonElement.on_click') + ); + assert.deepEqual(log[1], 1); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte new file mode 100644 index 000000000..ca4627350 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte @@ -0,0 +1,11 @@ +<script> + let x = $state(0); + let y = $state(0); + + $inspect(x, (x, type) => { + if (type === 'update') console.log(new Error(), x); + }); +</script> + +<button on:click={() => x++}>{x}</button> +<button on:click={() => y++}>{y}</button> diff --git a/packages/svelte/tests/runtime-runes/samples/inspect/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect/_config.js new file mode 100644 index 000000000..00a43e404 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect/_config.js @@ -0,0 +1,34 @@ +import { test } from '../../test'; + +/** + * @type {any[]} + */ +let log; +/** + * @type {typeof console.log}} + */ +let original_log; + +export default test({ + compileOptions: { + dev: true + }, + before_test() { + log = []; + original_log = console.log; + console.log = (...v) => { + log.push(...v); + }; + }, + after_test() { + console.log = original_log; + }, + async test({ assert, target, component }) { + const [b1, b2] = target.querySelectorAll('button'); + b1.click(); + b2.click(); + await Promise.resolve(); + + assert.deepEqual(log, [0, 'init', 1, 'update']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect/main.svelte new file mode 100644 index 000000000..739e3f8a2 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect/main.svelte @@ -0,0 +1,9 @@ +<script> + let x = $state(0); + let y = $state(0); + + $inspect(x); +</script> + +<button on:click={() => x++}>{x}</button> +<button on:click={() => y++}>{y}</button> diff --git a/packages/svelte/tests/runtime-runes/samples/member-mutation/_config.js b/packages/svelte/tests/runtime-runes/samples/member-mutation/_config.js new file mode 100644 index 000000000..8c2b03e0b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/member-mutation/_config.js @@ -0,0 +1,19 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: `<button>person.name.first = "dave"</button><h3>JSON output</h3><div>[{"name":{"first":"rob"}}]</div>`, + + async test({ assert, target }) { + const button = target.querySelector('button'); + + flushSync(() => { + button?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `<button>person.name.first = "dave"</button><h3>JSON output</h3><div>[{"name":{"first":"dave"}}]</div>` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/member-mutation/main.svelte b/packages/svelte/tests/runtime-runes/samples/member-mutation/main.svelte new file mode 100644 index 000000000..8d7932f3f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/member-mutation/main.svelte @@ -0,0 +1,15 @@ +<script> + let people = $state([{name:{first:'rob'}}]); +</script> + +{#each people as person} + <button on:click={()=>{ + person.name.first = "dave"; + people = people; + }}>person.name.first = "dave"</button> +{/each} + +<h3>JSON output</h3> +{#each people as person} + <div>{JSON.stringify(people)}</div> +{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/nullish-operator/_config.js b/packages/svelte/tests/runtime-runes/samples/nullish-operator/_config.js index 90c7fd7f3..4c425bc5f 100644 --- a/packages/svelte/tests/runtime-runes/samples/nullish-operator/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/nullish-operator/_config.js @@ -8,6 +8,6 @@ export default test({ async test({ assert, target, component }) { await Promise.resolve(); await Promise.resolve(); - assert.deepEqual(component.log, ['a1: ', {}, 'b1: ', {}]); + assert.deepEqual(component.log, ['a1: ', true, 'b1: ', true]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/nullish-operator/main.svelte b/packages/svelte/tests/runtime-runes/samples/nullish-operator/main.svelte index 8eb78927c..892973063 100644 --- a/packages/svelte/tests/runtime-runes/samples/nullish-operator/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/nullish-operator/main.svelte @@ -3,26 +3,26 @@ let b1 = $state(); const {log} = $props(); - + $effect(() => { log.push('a1: ', a1); }); $effect(() => { log.push('b1: ', b1); }); - + a(); queueMicrotask(a); - + b(); queueMicrotask(b); - + function a() { - a1 ??= {}; + a1 ??= true; } - + function b() { - b1 ?? (b1 = {}); + b1 ?? (b1 = true); } </script> diff --git a/packages/svelte/tests/runtime-runes/samples/props-derived/Child.svelte b/packages/svelte/tests/runtime-runes/samples/props-derived/Child.svelte new file mode 100644 index 000000000..f1914c86b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-derived/Child.svelte @@ -0,0 +1,7 @@ +<script> + let { random } = $props(); +</script> + +<p>{random}</p> +<p>{random}</p> +<p>{random}</p> diff --git a/packages/svelte/tests/runtime-runes/samples/props-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/props-derived/_config.js new file mode 100644 index 000000000..53d312292 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-derived/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +let math_random = Math.random; +let calls = 0; + +export default test({ + skip_if_hydrate: 'permanent', + before_test() { + Math.random = function () { + calls++; + return math_random.call(this); + }; + }, + after_test() { + Math.random = math_random; + calls = 0; + }, + test({ assert }) { + assert.equal(calls, 1); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-derived/main.svelte new file mode 100644 index 000000000..76bf2ba49 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-derived/main.svelte @@ -0,0 +1,5 @@ +<script> + import Child from './Child.svelte'; +</script> + +<Child random={Math.random().toFixed(2)} /> diff --git a/packages/svelte/tests/runtime-runes/samples/props-spread-fallback/Component.svelte b/packages/svelte/tests/runtime-runes/samples/props-spread-fallback/Component.svelte new file mode 100644 index 000000000..6346eb574 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-spread-fallback/Component.svelte @@ -0,0 +1,5 @@ +<script> + const { propA, propB = "fallback" } = $props(); +</script> + +<p>{propA} {propB}</p> diff --git a/packages/svelte/tests/runtime-runes/samples/props-spread-fallback/_config.js b/packages/svelte/tests/runtime-runes/samples/props-spread-fallback/_config.js new file mode 100644 index 000000000..f6be4fbae --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-spread-fallback/_config.js @@ -0,0 +1,55 @@ +import { test } from '../../test'; + +// Tests that fallback values are kept as long as the prop is not defined in the case of a spread +export default test({ + accessors: false, // so that propA actually becomes $.prop and not $.prop_source + html: ` + <button>change propA</button> + <button>change propB</button> + <p>true fallback</p> + `, + + async test({ assert, target }) { + const [propA, propB] = target.querySelectorAll('button'); + + await propA.click(); + assert.htmlEqual( + target.innerHTML, + ` + <button>change propA</button> + <button>change propB</button> + <p>false fallback</p> + ` + ); + + await propB.click(); + assert.htmlEqual( + target.innerHTML, + ` + <button>change propA</button> + <button>change propB</button> + <p>false defined</p> + ` + ); + + await propA.click(); + assert.htmlEqual( + target.innerHTML, + ` + <button>change propA</button> + <button>change propB</button> + <p>true defined</p> + ` + ); + + await propB.click(); + assert.htmlEqual( + target.innerHTML, + ` + <button>change propA</button> + <button>change propB</button> + <p>true</p> + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-spread-fallback/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-spread-fallback/main.svelte new file mode 100644 index 000000000..e3fd384d5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-spread-fallback/main.svelte @@ -0,0 +1,12 @@ +<script> + import Component from './Component.svelte'; + + let props = $state({ + propA: true, + propB: undefined + }); +</script> + +<button on:click={() => {props.propA = !props.propA}}>change propA</button> +<button on:click={() => {props.propB = props.propB ? undefined : 'defined'}}>change propB</button> +<Component {...props}/> diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-array/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-array/_config.js new file mode 100644 index 000000000..e878dad88 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-array/_config.js @@ -0,0 +1,66 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` + <button>1 + 2 + 3 = 6</button> + <button>clear</button> + <button>reverse</button> + <span>1</span> + <span>2</span> + <span>3</span> + <strong>array[1]: 2</strong> + `, + + async test({ assert, target }) { + const [add, clear, reverse] = target.querySelectorAll('button'); + + await add?.click(); + assert.htmlEqual( + target.innerHTML, + ` + <button>1 + 2 + 3 + 4 = 10</button> + <button>clear</button> + <button>reverse</button> + <span>1</span> + <span>2</span> + <span>3</span> + <span>4</span> + <strong>array[1]: 2</strong> + ` + ); + + flushSync(() => { + reverse?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + ` + <button>4 + 3 + 2 + 1 = 10</button> + <button>clear</button> + <button>reverse</button> + <span>4</span> + <span>3</span> + <span>2</span> + <span>1</span> + <strong>array[1]: 3</strong> + ` + ); + + flushSync(() => { + clear?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + ` + <button>4 = 4</button> + <button>clear</button> + <button>reverse</button> + <span>4</span> + <strong>array[1]:</strong> + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-array/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-array/main.svelte new file mode 100644 index 000000000..f2a9f5cfc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-array/main.svelte @@ -0,0 +1,22 @@ +<script> + const array = $state([1, 2, 3]); + const sum = $derived(array.reduce((a, b) => a + b, 0)); +</script> + +<button onclick={() => array.push(array.length + 1)}> + {array.join(' + ')} = {sum} +</button> + +<button onclick={() => array.length = 1}> + clear +</button> + +<button onclick={() => array.reverse()}> + reverse +</button> + +{#each array as n} + <span>{n}</span> +{/each} + +<strong>array[1]: {array[1]}</strong> diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/_config.js new file mode 100644 index 000000000..185a198ca --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + html: `<button>hello!</button>`, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual(target.innerHTML, `<button>goodbye!</button>`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/main.svelte new file mode 100644 index 000000000..f15777296 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/main.svelte @@ -0,0 +1,8 @@ +<script> + const ping = $state({}); + ping.pong = { ping, pang: 'hello!' }; +</script> + +<button onclick={() => ping.pong.pang = 'goodbye!'}> + {ping.pong.ping.pong.ping.pong.pang} +</button> diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-deep/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-deep/_config.js new file mode 100644 index 000000000..25a717285 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-deep/_config.js @@ -0,0 +1,39 @@ +import { test } from '../../test'; + +export default test({ + html: ` + <button>1</button> + <button>double</button> + `, + + async test({ assert, target }) { + const [btn1, btn2] = target.querySelectorAll('button'); + + await btn1?.click(); + assert.htmlEqual( + target.innerHTML, + ` + <button>2</button> + <button>double</button> + ` + ); + + await btn2?.click(); + assert.htmlEqual( + target.innerHTML, + ` + <button>4</button> + <button>double</button> + ` + ); + + await btn1?.click(); + assert.htmlEqual( + target.innerHTML, + ` + <button>5</button> + <button>double</button> + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-deep/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-deep/main.svelte new file mode 100644 index 000000000..0acba3173 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-deep/main.svelte @@ -0,0 +1,17 @@ +<script> + const object = $state({ + foo: { + bar: { + baz: 1 + } + } + }); +</script> + +<button onclick={() => object.foo.bar.baz += 1}> + {object.foo.bar.baz} +</button> + +<button onclick={() => object.foo.bar = { baz: object.foo.bar.baz * 2 }}> + double +</button> diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-nested/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-nested/_config.js new file mode 100644 index 000000000..fdf60d91f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-nested/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + html: `<button>clicks: 0</button>`, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual(target.innerHTML, `<button>clicks: 1</button>`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-nested/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-nested/main.svelte new file mode 100644 index 000000000..a0f78ba3b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-nested/main.svelte @@ -0,0 +1,15 @@ +<script> + const inner = $state({ + count: 0 + }) + + const object = $state({ + outer: { + inner + } + }); +</script> + +<button onclick={() => inner.count += 1}> + clicks: {object.outer.inner.count} +</button> diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/Counter.svelte new file mode 100644 index 000000000..d1be32683 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/Counter.svelte @@ -0,0 +1,8 @@ +<script> + /** @type {{ object: { count: number }}} */ + let { object } = $props(); +</script> + +<button onclick={() => object.count += 1}> + clicks: {object.count} +</button> diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/_config.js new file mode 100644 index 000000000..bdab6c9e3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +export default test({ + html: ` + <button>clicks: 0</button> + <p>object.count: 0</p> + `, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual( + target.innerHTML, + ` + <button>clicks: 1</button> + <p>object.count: 1</p> + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/main.svelte new file mode 100644 index 000000000..d9d6d7ec3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/main.svelte @@ -0,0 +1,9 @@ +<script> + import Counter from './Counter.svelte'; + + let object = $state({ count: 0 }); +</script> + +<Counter bind:object /> + +<p>object.count: {object.count}</p> diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/Counter.svelte new file mode 100644 index 000000000..d1be32683 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/Counter.svelte @@ -0,0 +1,8 @@ +<script> + /** @type {{ object: { count: number }}} */ + let { object } = $props(); +</script> + +<button onclick={() => object.count += 1}> + clicks: {object.count} +</button> diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js new file mode 100644 index 000000000..5e0eb0e2d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js @@ -0,0 +1,18 @@ +import { test } from '../../test'; + +export default test({ + html: `<button>clicks: 0</button>`, + + compileOptions: { + dev: true + }, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual(target.innerHTML, `<button>clicks: 0</button>`); + }, + + runtime_error: 'Props are read-only, unless used with `bind:`' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/main.svelte new file mode 100644 index 000000000..e9617af17 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/main.svelte @@ -0,0 +1,7 @@ +<script> + import Counter from './Counter.svelte'; + + const object = $state({ count: 0 }); +</script> + +<Counter {object} /> diff --git a/packages/svelte/tests/runtime-runes/samples/proxy/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy/_config.js new file mode 100644 index 000000000..fdf60d91f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + html: `<button>clicks: 0</button>`, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual(target.innerHTML, `<button>clicks: 1</button>`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy/main.svelte new file mode 100644 index 000000000..b83d9ec2b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy/main.svelte @@ -0,0 +1,7 @@ +<script> + const object = $state({ count: 0 }); +</script> + +<button onclick={() => object.count += 1}> + clicks: {object.count} +</button> diff --git a/packages/svelte/tests/runtime-runes/samples/slot/_config.js b/packages/svelte/tests/runtime-runes/samples/slot/_config.js new file mode 100644 index 000000000..c69314e24 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/slot/_config.js @@ -0,0 +1,6 @@ +import { test } from '../../test'; + +// Test that $$slots exists in runes mode +export default test({ + html: `<p>bar</p>` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/slot/main.svelte b/packages/svelte/tests/runtime-runes/samples/slot/main.svelte new file mode 100644 index 000000000..e6490ab09 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/slot/main.svelte @@ -0,0 +1,7 @@ +<script> + import Slot from './slot.svelte'; +</script> + +<Slot> + <p>bar</p> +</Slot> diff --git a/packages/svelte/tests/runtime-runes/samples/slot/slot.svelte b/packages/svelte/tests/runtime-runes/samples/slot/slot.svelte new file mode 100644 index 000000000..f58504ca4 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/slot/slot.svelte @@ -0,0 +1,3 @@ +{#if $$slots} + <slot></slot> +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-duplicate-children/Child.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-duplicate-children/Child.svelte new file mode 100644 index 000000000..441f7f5ee --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-duplicate-children/Child.svelte @@ -0,0 +1,4 @@ +<script> + const {children} = $props(); +</script> +<div>{@render children()}</div> diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-duplicate-children/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-duplicate-children/_config.js new file mode 100644 index 000000000..1c5430b02 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-duplicate-children/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `<div>Hello</div><div>World</div>` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-duplicate-children/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-duplicate-children/main.svelte new file mode 100644 index 000000000..6d745ccc9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-duplicate-children/main.svelte @@ -0,0 +1,15 @@ +<script> + import Child from './Child.svelte'; +</script> + +<Child> + {#snippet children()} + Hello + {/snippet} +</Child> + +<Child> + {#snippet children()} + World + {/snippet} +</Child> diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-dynamic-children/Button.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-dynamic-children/Button.svelte new file mode 100644 index 000000000..bf8ea31e9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-dynamic-children/Button.svelte @@ -0,0 +1,7 @@ +<script> + const { children, change } = $props(); +</script> + +<button onclick={change}> + {@render children()} +</button> diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-dynamic-children/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-dynamic-children/_config.js new file mode 100644 index 000000000..cd15ff721 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-dynamic-children/_config.js @@ -0,0 +1,16 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: `<button><span>hidden</span></button>`, + + async test({ assert, target }) { + const [b1] = target.querySelectorAll('button'); + + flushSync(() => { + b1?.click(); + }); + + assert.htmlEqual(target.innerHTML, `<button><span>showing</span></button>`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-dynamic-children/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-dynamic-children/main.svelte new file mode 100644 index 000000000..55a095bf3 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-dynamic-children/main.svelte @@ -0,0 +1,13 @@ +<script> + import Button from "./Button.svelte"; + + let show = $state(false); +</script> + +<Button change={() => show = true}> + {#if show} + <span>showing</span> + {:else} + <span>hidden</span> + {/if} +</Button> diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-reactive-args/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-reactive-args/_config.js new file mode 100644 index 000000000..ffb50aaa5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-reactive-args/_config.js @@ -0,0 +1,62 @@ +import { test } from '../../test'; + +export default test({ + html: ` + <p>snippet: 0</p> + <button>toggle</button> + <button>increase count</button> + `, + props: { + get log() { + return []; + } + }, + + async test({ assert, target, component }) { + const [toggle, increment] = target.querySelectorAll('button'); + + await increment?.click(); + assert.htmlEqual( + target.innerHTML, + ` + <p>snippet: 1</p> + <button>toggle</button> + <button>increase count</button> + ` + ); + assert.deepEqual(component.log, []); + + await toggle?.click(); + assert.htmlEqual( + target.innerHTML, + ` + <p>component: 1</p> + <button>toggle</button> + <button>increase count</button> + ` + ); + assert.deepEqual(component.log, [1]); + + await increment?.click(); + assert.htmlEqual( + target.innerHTML, + ` + <p>component: 2</p> + <button>toggle</button> + <button>increase count</button> + ` + ); + assert.deepEqual(component.log, [1]); + + await toggle?.click(); + assert.htmlEqual( + target.innerHTML, + ` + <p>snippet: 2</p> + <button>toggle</button> + <button>increase count</button> + ` + ); + assert.deepEqual(component.log, [1]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-reactive-args/inner.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-reactive-args/inner.svelte new file mode 100644 index 000000000..bbe3a6104 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-reactive-args/inner.svelte @@ -0,0 +1,6 @@ +<script> + let { count, log } = $props(); + log.push(count); +</script> + +<p>component: {count}</p> diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-reactive-args/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-reactive-args/main.svelte new file mode 100644 index 000000000..682fca22d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-reactive-args/main.svelte @@ -0,0 +1,22 @@ +<script> + import Inner from "./inner.svelte"; + + let { log } = $props(); + + let count = $state(0); + let show_foo = $state(true); + let snippet = $derived(show_foo ? foo : bar); +</script> + +{#snippet foo({count})} + <p>snippet: {count}</p> +{/snippet} + +{#snippet bar(props)} + <Inner {...props}></Inner> +{/snippet} + +{@render snippet({ count, log })} + +<button onclick={() => show_foo = !show_foo}>toggle</button> +<button onclick={() => count++}>increase count</button> diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-scope/Component.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-scope/Component.svelte new file mode 100644 index 000000000..79a2ed25e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-scope/Component.svelte @@ -0,0 +1,7 @@ +<script> + let { inner } = $props(); +</script> + +{#if inner} + {@render inner()} +{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-scope/_config.js b/packages/svelte/tests/runtime-runes/samples/snippet-scope/_config.js new file mode 100644 index 000000000..6f04c6270 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-scope/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `<p>S</p><a href="#">S</a><p>v</p><a href="#">v</a><p>e</p><a href="#">e</a><p>l</p><a href="#">l</a><p>t</p><a href="#">t</a><p>e</p><a href="#">e</a>` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-scope/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-scope/main.svelte new file mode 100644 index 000000000..737d2943e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-scope/main.svelte @@ -0,0 +1,13 @@ +<script> + import Component from './Component.svelte'; + let name = "Svelte"; +</script> + +{#each name.split('') as character} + <p>{character}</p> + <Component> + {#snippet inner()} + <a href={'#'}>{character}</a> + {/snippet} + </Component> +{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/_config.js b/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/_config.js new file mode 100644 index 000000000..4fd52f2d5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: '1 2' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/main.svelte b/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/main.svelte new file mode 100644 index 000000000..015c2d979 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/main.svelte @@ -0,0 +1,6 @@ +<script lang="ts"> + let count = $state(1) as number; + let double = $derived(count as number * 2) as number; +</script> + +{count as number} {double as number} diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-const1/_config.js b/packages/svelte/tests/runtime-runes/samples/typescript-const1/_config.js new file mode 100644 index 000000000..b34c10c8f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/typescript-const1/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: '<p>10 * 10 = 100</p><p>{}</p><p>20 * 20 = 400</p><p>{}</p>' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-const1/main.svelte b/packages/svelte/tests/runtime-runes/samples/typescript-const1/main.svelte new file mode 100644 index 000000000..04d555cab --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/typescript-const1/main.svelte @@ -0,0 +1,10 @@ +<script lang="ts"> + const boxes = [ { width: 10, height: 10 }, { width: 20, height: 20 } ]; +</script> + +{#each boxes as box} + {@const area: number = box.width * box.height} + {@const name: string = "{}"} + <p>{box.width} * {box.height} = {area}</p> + <p>{name}</p> +{/each} diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/_config.js b/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/_config.js new file mode 100644 index 000000000..4fd52f2d5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: '1 2' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/main.svelte b/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/main.svelte new file mode 100644 index 000000000..e851a5782 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/main.svelte @@ -0,0 +1,6 @@ +<script lang="ts"> + let count = $state(1)!; + let double = $derived(count! * 2)!; +</script> + +{count!} {double!} diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index ab886d565..8f8f96c12 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -112,10 +112,7 @@ describe('signals', () => { const A = $.source(0); const B = $.source(0); const C = $.derived(() => ($.get(A) % 2) + ($.get(B) % 2)); - const D = $.derived( - () => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2)), - (l: number[], r: number[]) => l.length === r.length && l.every((v, i) => v === r[i]) - ); + const D = $.derived(() => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2))); const E = $.derived(() => hard($.get(C) + $.get(A) + $.get(D)[0]!, 'E')); const F = $.derived(() => hard($.get(D)[0]! && $.get(B), 'F')); const G = $.derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[0]! + $.get(F)); diff --git a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js index 836fd0ed0..4add64c66 100644 --- a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js @@ -26,4 +26,4 @@ export default function Class_state_field_constructor_assignment($$anchor, $$pro } $.pop(); -} +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js new file mode 100644 index 000000000..713b4e133 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js @@ -0,0 +1,48 @@ +// main.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import "svelte/internal/disclose-version"; +import * as $ from "svelte/internal"; + +var frag = $.template(`<div></div> <svg></svg> <custom-element></custom-element> <div></div> <svg></svg> <custom-element></custom-element>`, true); + +export default function Main($$anchor, $$props) { + $.push($$props, true); + + // needs to be a snapshot test because jsdom does auto-correct the attribute casing + let x = 'test'; + let y = () => 'test'; + /* Init */ + var fragment = $.open_frag($$anchor, false, frag); + var node = $.child_frag(fragment); + var svg = $.sibling($.sibling(node)); + var custom_element = $.sibling($.sibling(svg)); + var div = $.sibling($.sibling(custom_element)); + var svg_1 = $.sibling($.sibling(div)); + var custom_element_1 = $.sibling($.sibling(svg_1)); + + /* Update */ + $.attr_effect(div, "foobar", () => y()); + $.attr_effect(svg_1, "viewBox", () => y()); + $.set_custom_element_data_effect(custom_element_1, "fooBar", () => y()); + + var node_foobar; + var svg_viewBox; + var custom_element_fooBar; + + $.render_effect(() => { + if (node_foobar !== (node_foobar = x)) { + $.attr(node, "foobar", node_foobar); + } + + if (svg_viewBox !== (svg_viewBox = x)) { + $.attr(svg, "viewBox", svg_viewBox); + } + + if (custom_element_fooBar !== (custom_element_fooBar = x)) { + $.set_custom_element_data(custom_element, "fooBar", custom_element_fooBar); + } + }); + + $.close_frag($$anchor, fragment); + $.pop(); +} diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js new file mode 100644 index 000000000..0352ce77a --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js @@ -0,0 +1,14 @@ +// main.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import * as $ from "svelte/internal/server"; + +export default function Main($$payload, $$props) { + $.push(true); + + // needs to be a snapshot test because jsdom does auto-correct the attribute casing + let x = 'test'; + let y = () => 'test'; + + $$payload.out += `<div${$.attr("foobar", x, false)}></div> <svg${$.attr("viewBox", x, false)}></svg> <custom-element${$.attr("foobar", x, false)}></custom-element> <div${$.attr("foobar", y(), false)}></div> <svg${$.attr("viewBox", y(), false)}></svg> <custom-element${$.attr("foobar", y(), false)}></custom-element>`; + $.pop(); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/main.svelte b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/main.svelte new file mode 100644 index 000000000..a38659a48 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/main.svelte @@ -0,0 +1,14 @@ +<script> + // needs to be a snapshot test because jsdom does auto-correct the attribute casing + let x = $state('test'); + let y = $state(() => 'test'); +</script> + +<div fooBar={x}></div> +<svg viewBox={x}></svg> +<custom-element fooBar={x}></custom-element> + +<!-- force them into singular render effects by using function invocations --> +<div fooBar={y()}></div> +<svg viewBox={y()} ></svg> +<custom-element fooBar={y()}></custom-element> diff --git a/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js new file mode 100644 index 000000000..c9ba1e7c7 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js @@ -0,0 +1,4 @@ +/* index.svelte.js generated by Svelte VERSION */ +import * as $ from "svelte/internal"; + +export const object = $.proxy({ ok: true }); diff --git a/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js new file mode 100644 index 000000000..770f2e5e4 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js @@ -0,0 +1,5 @@ +/* index.svelte.js generated by Svelte VERSION */ +import * as $ from "svelte/internal/server"; + +export const object = { ok: true }; + diff --git a/packages/svelte/tests/snapshot/samples/export-state/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/index.svelte.js new file mode 100644 index 000000000..023fcb9b3 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/export-state/index.svelte.js @@ -0,0 +1,3 @@ +export const object = $state({ + ok: true +}); diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js index 3f92e7652..8fb2fbcf6 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js @@ -3,36 +3,32 @@ import "svelte/internal/disclose-version"; import * as $ from "svelte/internal"; -var Button_default = $.template(` `, true); -var frag = $.template(`<!>`, true); - export default function Function_prop_no_getter($$anchor, $$props) { $.push($$props, true); let count = $.source(0); function onmouseup() { - $.set(count, $.get(count) + 2); + $.set(count, $.proxy($.get(count) + 2)); } /* Init */ - var fragment = $.open_frag($$anchor, true, frag); + var fragment = $.comment($$anchor); var node = $.child_frag(fragment); Button(node, { - onmousedown: () => $.set(count, $.get(count) + 1), + onmousedown: () => $.set(count, $.proxy($.get(count) + 1)), onmouseup, children: ($$anchor, $$slotProps) => { /* Init */ - var fragment_1 = $.open_frag($$anchor, true, Button_default); - var text = $.child_frag(fragment_1); + var node_1 = $.space($$anchor); /* Update */ - $.text_effect(text, () => `clicks: ${$.stringify($.get(count))}`); - $.close_frag($$anchor, fragment_1); + $.text_effect(node_1, () => `clicks: ${$.stringify($.get(count))}`); + $.close($$anchor, node_1); } }); $.close_frag($$anchor, fragment); $.pop(); -} +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js index 578409e0a..ffd6a820b 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js @@ -25,4 +25,4 @@ export default function Function_prop_no_getter($$payload, $$props) { $$payload.out += `${anchor}`; $.pop(); -} +} \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/_config.js b/packages/svelte/tests/snapshot/samples/svelte-element/_config.js similarity index 100% rename from packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/_config.js rename to packages/svelte/tests/snapshot/samples/svelte-element/_config.js diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js new file mode 100644 index 000000000..b4df70d14 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js @@ -0,0 +1,17 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import "svelte/internal/disclose-version"; +import * as $ from "svelte/internal"; + +export default function Svelte_element($$anchor, $$props) { + $.push($$props, true); + + let tag = $.prop_source($$props, "tag", true, 'hr'); + /* Init */ + var fragment = $.comment($$anchor); + var node = $.child_frag(fragment); + + $.element(node, () => $.get(tag)); + $.close_frag($$anchor, fragment); + $.pop(); +} diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js new file mode 100644 index 000000000..405a45227 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js @@ -0,0 +1,27 @@ +// index.svelte (Svelte VERSION) +// Note: compiler output will change before 5.0 is released! +import * as $ from "svelte/internal/server"; + +export default function Svelte_element($$payload, $$props) { + $.push(true); + + let { tag = 'hr' } = $$props; + const anchor = $.create_anchor($$payload); + + $$payload.out += `${anchor}`; + + if (tag) { + const anchor_1 = $.create_anchor($$payload); + + $$payload.out += `<${tag}>`; + + if (!$.VoidElements.has(tag)) { + $$payload.out += `${anchor_1}`; + $$payload.out += `${anchor_1}</${tag}>`; + } + } + + $$payload.out += `${anchor}`; + $.bind_props($$props, { tag }); + $.pop(); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/index.svelte b/packages/svelte/tests/snapshot/samples/svelte-element/index.svelte new file mode 100644 index 000000000..cd543173a --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/svelte-element/index.svelte @@ -0,0 +1,5 @@ +<script> + let { tag = 'hr' } = $props(); +</script> + +<svelte:element this={tag} /> diff --git a/packages/svelte/tests/types/component.ts b/packages/svelte/tests/types/component.ts index 749c648e2..1047d5678 100644 --- a/packages/svelte/tests/types/component.ts +++ b/packages/svelte/tests/types/component.ts @@ -4,7 +4,8 @@ import { SvelteComponent, type ComponentEvents, type ComponentProps, - type ComponentType + type ComponentType, + mount } from 'svelte'; // --------------------------------------------------------------------------- legacy: classes @@ -15,11 +16,11 @@ class LegacyComponent extends SvelteComponent< { slot: { slotProps: boolean } } > {} -// @ts-expect-error const legacyComponent = new LegacyComponent({ target: null as any as Document | Element | ShadowRoot, props: { prop: 'foo', + // @ts-expect-error x: '' } }); @@ -56,14 +57,20 @@ class NewComponent extends SvelteComponent< anExport: string = ''; } -// @ts-expect-error new NewComponent({ - prop: 'foo', - x: '' + target: null as any, + props: { + prop: 'foo', + // @ts-expect-error + x: '' + } }); const newComponent: NewComponent = new NewComponent({ - prop: 'foo' + target: null as any, + props: { + prop: 'foo' + } }); newComponent.$$events_def.event; // @ts-expect-error @@ -97,7 +104,22 @@ const newComponentEvents2: ComponentEvents<NewComponent> = { event: new KeyboardEvent('click') }; -const instance = createRoot(newComponent, { +mount(NewComponent, { + target: null as any as Document | Element | ShadowRoot | Text | Comment, + props: { + prop: 'foo', + // @ts-expect-error + x: '' + }, + events: { + event: new MouseEvent('click') + }, + immutable: true, + intro: false, + recover: false +}); + +const instance = createRoot(NewComponent, { target: null as any as Document | Element | ShadowRoot | Text | Comment, props: { prop: 'foo', @@ -123,11 +145,11 @@ instance.anExport === 1; // --------------------------------------------------------------------------- interop const AsLegacyComponent = asClassComponent(newComponent); -// @ts-expect-error new AsLegacyComponent({ target: null as any, props: { prop: '', + // @ts-expect-error x: '' } }); diff --git a/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/input.svelte b/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/input.svelte index 3fb1ded53..0452a3714 100644 --- a/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/input.svelte +++ b/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/input.svelte @@ -24,6 +24,8 @@ <header on:click={noop} /> <!-- svelte-ignore a11y-no-noninteractive-element-interactions --> <footer on:click={noop} /> +<!-- svelte-ignore a11y-no-noninteractive-element-interactions --> +<footer onclick={noop} /> <!-- should not warn --> <div class="foo" /> @@ -66,6 +68,7 @@ <div on:click={noop} role="presentation" /> <div on:click={noop} role="none" /> <div on:click={noop} role={dynamicRole} /> +<div onclick={noop} role={dynamicRole} /> <!-- svelte-ignore a11y-no-static-element-interactions --> <svelte:element this={Math.random() ? 'button' : 'div'} on:click={noop} /> diff --git a/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/warnings.json b/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/warnings.json index d5f3df99d..82309f949 100644 --- a/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-click-events-have-key-events/warnings.json @@ -1,7 +1,7 @@ [ { "code": "a11y-click-events-have-key-events", - "message": "A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", "start": { "line": 13, "column": 0 @@ -13,7 +13,7 @@ }, { "code": "a11y-click-events-have-key-events", - "message": "A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", "start": { "line": 15, "column": 0 @@ -25,7 +25,7 @@ }, { "code": "a11y-click-events-have-key-events", - "message": "A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", "start": { "line": 18, "column": 0 @@ -37,7 +37,7 @@ }, { "code": "a11y-click-events-have-key-events", - "message": "A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", "start": { "line": 20, "column": 0 @@ -49,7 +49,7 @@ }, { "code": "a11y-click-events-have-key-events", - "message": "A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", "start": { "line": 22, "column": 0 @@ -61,7 +61,7 @@ }, { "code": "a11y-click-events-have-key-events", - "message": "A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", "start": { "line": 24, "column": 0 @@ -73,7 +73,7 @@ }, { "code": "a11y-click-events-have-key-events", - "message": "A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", "start": { "line": 26, "column": 0 @@ -82,5 +82,17 @@ "line": 26, "column": 26 } + }, + { + "code": "a11y-click-events-have-key-events", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "start": { + "line": 28, + "column": 0 + }, + "end": { + "line": 28, + "column": 25 + } } ] diff --git a/packages/svelte/tests/validator/samples/a11y-contenteditable-element-without-child/input.svelte b/packages/svelte/tests/validator/samples/a11y-contenteditable-element-without-child/input.svelte index 0702c3cb4..101e08e7a 100644 --- a/packages/svelte/tests/validator/samples/a11y-contenteditable-element-without-child/input.svelte +++ b/packages/svelte/tests/validator/samples/a11y-contenteditable-element-without-child/input.svelte @@ -3,4 +3,4 @@ </script> <p bind:textContent={text} contenteditable="true"></p> <p bind:innerHTML={text} contenteditable="true"></p> -<p bind:innerHTML={text}></p> +<p bind:innerHTML={text} contenteditable="true"></p> diff --git a/packages/svelte/tests/validator/samples/a11y-interactive-supports-focus/warnings.json b/packages/svelte/tests/validator/samples/a11y-interactive-supports-focus/warnings.json index 41586b064..a1b566851 100644 --- a/packages/svelte/tests/validator/samples/a11y-interactive-supports-focus/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-interactive-supports-focus/warnings.json @@ -233,7 +233,7 @@ "column": 44, "line": 29 }, - "message": "A11y: on:mouseout must be accompanied by on:blur", + "message": "A11y: 'mouseout' event must be accompanied by 'blur' event", "start": { "column": 0, "line": 29 @@ -257,7 +257,7 @@ "column": 48, "line": 30 }, - "message": "A11y: on:mouseover must be accompanied by on:focus", + "message": "A11y: 'mouseover' event must be accompanied by 'focus' event", "start": { "column": 0, "line": 30 diff --git a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json index f81b63559..316cca969 100644 --- a/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json +++ b/packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json @@ -5,7 +5,7 @@ "column": 35, "line": 11 }, - "message": "A11y: on:mouseover must be accompanied by on:focus", + "message": "A11y: 'mouseover' event must be accompanied by 'focus' event", "start": { "column": 0, "line": 11 @@ -17,7 +17,7 @@ "column": 51, "line": 15 }, - "message": "A11y: on:mouseover must be accompanied by on:focus", + "message": "A11y: 'mouseover' event must be accompanied by 'focus' event", "start": { "column": 0, "line": 15 @@ -29,7 +29,7 @@ "column": 34, "line": 17 }, - "message": "A11y: on:mouseout must be accompanied by on:blur", + "message": "A11y: 'mouseout' event must be accompanied by 'blur' event", "start": { "column": 0, "line": 17 @@ -41,7 +41,7 @@ "column": 50, "line": 21 }, - "message": "A11y: on:mouseout must be accompanied by on:blur", + "message": "A11y: 'mouseout' event must be accompanied by 'blur' event", "start": { "column": 0, "line": 21 diff --git a/packages/svelte/tests/validator/samples/action-on-component/_config.js b/packages/svelte/tests/validator/samples/action-on-component/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/action-on-component/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/action-on-component/errors.json b/packages/svelte/tests/validator/samples/action-on-component/errors.json index c85695f9b..6516cf910 100644 --- a/packages/svelte/tests/validator/samples/action-on-component/errors.json +++ b/packages/svelte/tests/validator/samples/action-on-component/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-action", - "message": "Actions can only be applied to DOM elements, not components", + "code": "invalid-component-directive", + "message": "This type of directive is not valid on components", "start": { "line": 7, "column": 8 diff --git a/packages/svelte/tests/validator/samples/animation-duplicate/_config.js b/packages/svelte/tests/validator/samples/animation-duplicate/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/animation-duplicate/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/animation-not-in-each/_config.js b/packages/svelte/tests/validator/samples/animation-not-in-each/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/animation-not-in-each/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/animation-not-in-keyed-each/_config.js b/packages/svelte/tests/validator/samples/animation-not-in-keyed-each/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/animation-not-in-keyed-each/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/animation-on-component/_config.js b/packages/svelte/tests/validator/samples/animation-on-component/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/animation-on-component/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/animation-on-component/errors.json b/packages/svelte/tests/validator/samples/animation-on-component/errors.json index b6e5ade74..eabf72bb0 100644 --- a/packages/svelte/tests/validator/samples/animation-on-component/errors.json +++ b/packages/svelte/tests/validator/samples/animation-on-component/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-animation", - "message": "Animations can only be applied to DOM elements, not components", + "code": "invalid-component-directive", + "message": "This type of directive is not valid on components", "start": { "line": 7, "column": 8 diff --git a/packages/svelte/tests/validator/samples/animation-siblings/_config.js b/packages/svelte/tests/validator/samples/animation-siblings/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/animation-siblings/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-2/_config.js b/packages/svelte/tests/validator/samples/assignment-to-const-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/assignment-to-const-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-2/errors.json b/packages/svelte/tests/validator/samples/assignment-to-const-2/errors.json index 7bbd36b9a..1b74f5156 100644 --- a/packages/svelte/tests/validator/samples/assignment-to-const-2/errors.json +++ b/packages/svelte/tests/validator/samples/assignment-to-const-2/errors.json @@ -1,7 +1,7 @@ [ { - "code": "assignment-to-const", - "message": "You are assigning to a const", + "code": "invalid-const-assignment", + "message": "Invalid assignment to const variable", "start": { "line": 13, "column": 24 diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-3/_config.js b/packages/svelte/tests/validator/samples/assignment-to-const-3/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/assignment-to-const-3/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-3/errors.json b/packages/svelte/tests/validator/samples/assignment-to-const-3/errors.json index 1201c10a8..b00dd3249 100644 --- a/packages/svelte/tests/validator/samples/assignment-to-const-3/errors.json +++ b/packages/svelte/tests/validator/samples/assignment-to-const-3/errors.json @@ -1,7 +1,7 @@ [ { - "code": "assignment-to-const", - "message": "You are assigning to a const", + "code": "invalid-const-assignment", + "message": "Invalid assignment to const variable", "start": { "line": 14, "column": 3 diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-4/_config.js b/packages/svelte/tests/validator/samples/assignment-to-const-4/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/assignment-to-const-4/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-4/errors.json b/packages/svelte/tests/validator/samples/assignment-to-const-4/errors.json index 39474b2f7..1fcfaf240 100644 --- a/packages/svelte/tests/validator/samples/assignment-to-const-4/errors.json +++ b/packages/svelte/tests/validator/samples/assignment-to-const-4/errors.json @@ -1,7 +1,7 @@ [ { - "code": "assignment-to-const", - "message": "You are assigning to a const", + "code": "invalid-const-assignment", + "message": "Invalid assignment to const variable", "start": { "line": 17, "column": 2 diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-5/_config.js b/packages/svelte/tests/validator/samples/assignment-to-const-5/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/assignment-to-const-5/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-5/errors.json b/packages/svelte/tests/validator/samples/assignment-to-const-5/errors.json index 715f33b63..9bff01645 100644 --- a/packages/svelte/tests/validator/samples/assignment-to-const-5/errors.json +++ b/packages/svelte/tests/validator/samples/assignment-to-const-5/errors.json @@ -1,7 +1,7 @@ [ { - "code": "assignment-to-const", - "message": "You are assigning to a const", + "code": "invalid-const-assignment", + "message": "Invalid assignment to const variable", "start": { "line": 3, "column": 1 diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-7/_config.js b/packages/svelte/tests/validator/samples/assignment-to-const-7/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/assignment-to-const-7/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/assignment-to-const-7/errors.json b/packages/svelte/tests/validator/samples/assignment-to-const-7/errors.json index 5ac8ad8b6..02e5503b1 100644 --- a/packages/svelte/tests/validator/samples/assignment-to-const-7/errors.json +++ b/packages/svelte/tests/validator/samples/assignment-to-const-7/errors.json @@ -1,7 +1,7 @@ [ { - "code": "assignment-to-const", - "message": "You are assigning to a const", + "code": "invalid-const-assignment", + "message": "Invalid assignment to const variable", "start": { "line": 3, "column": 1 diff --git a/packages/svelte/tests/validator/samples/assignment-to-const/_config.js b/packages/svelte/tests/validator/samples/assignment-to-const/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/assignment-to-const/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/assignment-to-const/errors.json b/packages/svelte/tests/validator/samples/assignment-to-const/errors.json index b822452b9..88d6b728b 100644 --- a/packages/svelte/tests/validator/samples/assignment-to-const/errors.json +++ b/packages/svelte/tests/validator/samples/assignment-to-const/errors.json @@ -1,7 +1,7 @@ [ { - "code": "assignment-to-const", - "message": "You are assigning to a const", + "code": "invalid-const-assignment", + "message": "Invalid assignment to const variable", "start": { "line": 16, "column": 2 diff --git a/packages/svelte/tests/validator/samples/attribute-expected-equals/_config.js b/packages/svelte/tests/validator/samples/attribute-expected-equals/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/attribute-expected-equals/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/attribute-expected-equals/errors.json b/packages/svelte/tests/validator/samples/attribute-expected-equals/errors.json index 9b4c0e80a..deb233162 100644 --- a/packages/svelte/tests/validator/samples/attribute-expected-equals/errors.json +++ b/packages/svelte/tests/validator/samples/attribute-expected-equals/errors.json @@ -1,7 +1,7 @@ [ { - "code": "unexpected-token", - "message": "Expected =", + "code": "expected-token", + "message": "Expected token =", "start": { "line": 5, "column": 9 diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name-2/_config.js b/packages/svelte/tests/validator/samples/attribute-invalid-name-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name-2/errors.json b/packages/svelte/tests/validator/samples/attribute-invalid-name-2/errors.json index 2c4e083c7..0e4afb300 100644 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name-2/errors.json +++ b/packages/svelte/tests/validator/samples/attribute-invalid-name-2/errors.json @@ -1,6 +1,6 @@ [ { - "code": "illegal-attribute", + "code": "invalid-attribute-name", "message": "'3aa' is not a valid attribute name", "start": { "line": 1, diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name-3/_config.js b/packages/svelte/tests/validator/samples/attribute-invalid-name-3/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name-3/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name-3/errors.json b/packages/svelte/tests/validator/samples/attribute-invalid-name-3/errors.json index f90739a52..c13a5c5b9 100644 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name-3/errors.json +++ b/packages/svelte/tests/validator/samples/attribute-invalid-name-3/errors.json @@ -1,6 +1,6 @@ [ { - "code": "illegal-attribute", + "code": "invalid-attribute-name", "message": "'a*a' is not a valid attribute name", "start": { "line": 1, diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name-4/_config.js b/packages/svelte/tests/validator/samples/attribute-invalid-name-4/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name-4/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name-4/errors.json b/packages/svelte/tests/validator/samples/attribute-invalid-name-4/errors.json index 1368e0c12..4cccb34b2 100644 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name-4/errors.json +++ b/packages/svelte/tests/validator/samples/attribute-invalid-name-4/errors.json @@ -1,6 +1,6 @@ [ { - "code": "illegal-attribute", + "code": "invalid-attribute-name", "message": "'-a' is not a valid attribute name", "start": { "line": 1, diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name-5/_config.js b/packages/svelte/tests/validator/samples/attribute-invalid-name-5/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name-5/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name-5/errors.json b/packages/svelte/tests/validator/samples/attribute-invalid-name-5/errors.json index 5d2d1390d..b99bb946a 100644 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name-5/errors.json +++ b/packages/svelte/tests/validator/samples/attribute-invalid-name-5/errors.json @@ -1,6 +1,6 @@ [ { - "code": "illegal-attribute", + "code": "invalid-attribute-name", "message": "'a;' is not a valid attribute name", "start": { "line": 1, diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name/_config.js b/packages/svelte/tests/validator/samples/attribute-invalid-name/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/attribute-invalid-name/errors.json b/packages/svelte/tests/validator/samples/attribute-invalid-name/errors.json index a688cba3d..5b674a6f7 100644 --- a/packages/svelte/tests/validator/samples/attribute-invalid-name/errors.json +++ b/packages/svelte/tests/validator/samples/attribute-invalid-name/errors.json @@ -1,6 +1,6 @@ [ { - "code": "illegal-attribute", + "code": "invalid-attribute-name", "message": "'}' is not a valid attribute name", "start": { "line": 1, diff --git a/packages/svelte/tests/validator/samples/binding-this-input-type-dynamic/errors.json b/packages/svelte/tests/validator/samples/binding-this-input-type-dynamic/errors.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/svelte/tests/validator/samples/binding-this-input-type-dynamic/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/binding-this-input-type-dynamic/input.svelte b/packages/svelte/tests/validator/samples/binding-this-input-type-dynamic/input.svelte new file mode 100644 index 000000000..70e0494c1 --- /dev/null +++ b/packages/svelte/tests/validator/samples/binding-this-input-type-dynamic/input.svelte @@ -0,0 +1,6 @@ +<script> + let foo; + let inputType; +</script> + +<input bind:this={foo} type={inputType}> \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/component-dynamic/options.json b/packages/svelte/tests/validator/samples/component-dynamic/options.json deleted file mode 100644 index 0b2a999c7..000000000 --- a/packages/svelte/tests/validator/samples/component-dynamic/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "generate": true -} diff --git a/packages/svelte/tests/validator/samples/component-event-modifiers-invalid/_config.js b/packages/svelte/tests/validator/samples/component-event-modifiers-invalid/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-event-modifiers-invalid/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-invalid-style-directive/_config.js b/packages/svelte/tests/validator/samples/component-invalid-style-directive/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-invalid-style-directive/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-invalid-style-directive/errors.json b/packages/svelte/tests/validator/samples/component-invalid-style-directive/errors.json index 8c4a68676..6c37a888f 100644 --- a/packages/svelte/tests/validator/samples/component-invalid-style-directive/errors.json +++ b/packages/svelte/tests/validator/samples/component-invalid-style-directive/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-component-style-directive", - "message": "Style directives cannot be used on components", + "code": "invalid-component-directive", + "message": "This type of directive is not valid on components", "start": { "line": 7, "column": 19 }, "end": { "line": 7, "column": 36 } } diff --git a/packages/svelte/tests/validator/samples/component-name-lowercase/_config.js b/packages/svelte/tests/validator/samples/component-name-lowercase/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-name-lowercase/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/_config.js b/packages/svelte/tests/validator/samples/component-slot-default-duplicate/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/errors.json b/packages/svelte/tests/validator/samples/component-slot-default-duplicate/errors.json deleted file mode 100644 index 8c093fd70..000000000 --- a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/errors.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "message": "duplicate default <slot> element", - "start": { - "line": 2, - "column": 0 - } - } -] diff --git a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/input.svelte b/packages/svelte/tests/validator/samples/component-slot-default-duplicate/input.svelte deleted file mode 100644 index 9dffac414..000000000 --- a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/input.svelte +++ /dev/null @@ -1,2 +0,0 @@ -<slot></slot> -<slot></slot> \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/component-slot-default-reserved/_config.js b/packages/svelte/tests/validator/samples/component-slot-default-reserved/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-slot-default-reserved/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/_config.js b/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/errors.json b/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/errors.json index cb15babd6..9194e8aa9 100644 --- a/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/errors.json +++ b/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/errors.json @@ -1,7 +1,7 @@ [ { "code": "invalid-slot-attribute", - "message": "slot attribute cannot have a dynamic value", + "message": "slot attribute must be a static value", "start": { "line": 6, "column": 9 diff --git a/packages/svelte/tests/validator/samples/component-slot-dynamic/_config.js b/packages/svelte/tests/validator/samples/component-slot-dynamic/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-slot-dynamic/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-dynamic/errors.json b/packages/svelte/tests/validator/samples/component-slot-dynamic/errors.json index 65b400437..963cb1f40 100644 --- a/packages/svelte/tests/validator/samples/component-slot-dynamic/errors.json +++ b/packages/svelte/tests/validator/samples/component-slot-dynamic/errors.json @@ -1,7 +1,7 @@ [ { - "code": "dynamic-slot-name", - "message": "<slot> name cannot be dynamic", + "code": "invalid-slot-name", + "message": "slot attribute must be a static value", "start": { "line": 1, "column": 6 diff --git a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/_config.js b/packages/svelte/tests/validator/samples/component-slot-named-duplicate/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/errors.json b/packages/svelte/tests/validator/samples/component-slot-named-duplicate/errors.json deleted file mode 100644 index e8152c360..000000000 --- a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/errors.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "message": "duplicate 'foo' <slot> element", - "start": { - "line": 2, - "column": 6 - } - } -] diff --git a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/input.svelte b/packages/svelte/tests/validator/samples/component-slot-named-duplicate/input.svelte deleted file mode 100644 index b0affc060..000000000 --- a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/input.svelte +++ /dev/null @@ -1,2 +0,0 @@ -<slot name='foo'></slot> -<slot name='foo'></slot> \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/_config.js b/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/errors.json b/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/errors.json index a0be2a67a..da10838a6 100644 --- a/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/errors.json +++ b/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/errors.json @@ -1,6 +1,6 @@ [ { - "code": "invalid-slotted-content", + "code": "invalid-slot-placement", "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element", "start": { "line": 10, "column": 9 }, "end": { "line": 10, "column": 19 } diff --git a/packages/svelte/tests/validator/samples/component-slotted-each-block/_config.js b/packages/svelte/tests/validator/samples/component-slotted-each-block/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-slotted-each-block/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slotted-each-block/errors.json b/packages/svelte/tests/validator/samples/component-slotted-each-block/errors.json index 38f4e6f67..12d240754 100644 --- a/packages/svelte/tests/validator/samples/component-slotted-each-block/errors.json +++ b/packages/svelte/tests/validator/samples/component-slotted-each-block/errors.json @@ -1,6 +1,6 @@ [ { - "code": "invalid-slotted-content", + "code": "invalid-slot-placement", "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element", "start": { "line": 7, diff --git a/packages/svelte/tests/validator/samples/component-slotted-if-block/_config.js b/packages/svelte/tests/validator/samples/component-slotted-if-block/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/component-slotted-if-block/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slotted-if-block/errors.json b/packages/svelte/tests/validator/samples/component-slotted-if-block/errors.json index 38f4e6f67..12d240754 100644 --- a/packages/svelte/tests/validator/samples/component-slotted-if-block/errors.json +++ b/packages/svelte/tests/validator/samples/component-slotted-if-block/errors.json @@ -1,6 +1,6 @@ [ { - "code": "invalid-slotted-content", + "code": "invalid-slot-placement", "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element", "start": { "line": 7, diff --git a/packages/svelte/tests/validator/samples/const-tag-conflict-1/_config.js b/packages/svelte/tests/validator/samples/const-tag-conflict-1/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/const-tag-conflict-1/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/const-tag-conflict-1/errors.json b/packages/svelte/tests/validator/samples/const-tag-conflict-1/errors.json index d92528566..78beca08c 100644 --- a/packages/svelte/tests/validator/samples/const-tag-conflict-1/errors.json +++ b/packages/svelte/tests/validator/samples/const-tag-conflict-1/errors.json @@ -1,8 +1,8 @@ [ { - "code": "invalid-const-declaration", + "code": "duplicate-declaration", "message": "'a' has already been declared", - "start": { "line": 7, "column": 2 }, - "end": { "line": 7, "column": 19 } + "start": { "line": 7, "column": 10 }, + "end": { "line": 7, "column": 11 } } ] diff --git a/packages/svelte/tests/validator/samples/const-tag-conflict-2/_config.js b/packages/svelte/tests/validator/samples/const-tag-conflict-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/const-tag-conflict-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/const-tag-conflict-2/errors.json b/packages/svelte/tests/validator/samples/const-tag-conflict-2/errors.json index 7636f8fcb..dc3c1322c 100644 --- a/packages/svelte/tests/validator/samples/const-tag-conflict-2/errors.json +++ b/packages/svelte/tests/validator/samples/const-tag-conflict-2/errors.json @@ -1,8 +1,8 @@ [ { - "code": "invalid-const-declaration", + "code": "duplicate-declaration", "message": "'item' has already been declared", - "start": { "line": 6, "column": 2 }, - "end": { "line": 6, "column": 21 } + "start": { "line": 6, "column": 10 }, + "end": { "line": 6, "column": 14 } } ] diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-1/_config.js b/packages/svelte/tests/validator/samples/const-tag-placement-1/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/const-tag-placement-1/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/const-tag-placement-2/_config.js b/packages/svelte/tests/validator/samples/const-tag-placement-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/const-tag-placement-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/const-tag-readonly-2/_config.js b/packages/svelte/tests/validator/samples/const-tag-readonly-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/const-tag-readonly-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/const-tag-readonly-2/errors.json b/packages/svelte/tests/validator/samples/const-tag-readonly-2/errors.json index 15307d637..a8c60a8b5 100644 --- a/packages/svelte/tests/validator/samples/const-tag-readonly-2/errors.json +++ b/packages/svelte/tests/validator/samples/const-tag-readonly-2/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-binding", - "message": "Cannot bind to a variable declared with {@const ...}", + "code": "invalid-const-assignment", + "message": "Invalid binding to const variable ($derived values, let: directives, :then/:catch variables and @const declarations count as const)", "start": { "line": 7, "column": 9 }, "end": { "line": 7, "column": 23 } } diff --git a/packages/svelte/tests/validator/samples/contenteditable-dynamic/_config.js b/packages/svelte/tests/validator/samples/contenteditable-dynamic/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/contenteditable-dynamic/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/contenteditable-missing/_config.js b/packages/svelte/tests/validator/samples/contenteditable-missing/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/contenteditable-missing/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-1/_config.js b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-1/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-1/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-1/errors.json b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-1/errors.json index 3bce9fbc0..fc90941b1 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-1/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-1/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-selector", - "message": "Invalid selector \"> span\"", + "code": "invalid-css-selector", + "message": "Invalid selector", "start": { "line": 10, "column": 1 }, "end": { "line": 10, "column": 7 } } diff --git a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-2/_config.js b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-2/errors.json b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-2/errors.json index 38305e106..aae67dc75 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-2/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-2/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-selector", - "message": "Invalid selector \"+ p\"", + "code": "invalid-css-selector", + "message": "Invalid selector", "start": { "line": 8, "column": 1 }, "end": { "line": 8, "column": 4 } } diff --git a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-3/_config.js b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-3/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-3/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-3/errors.json b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-3/errors.json index ad3572024..9f3d06660 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-3/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-3/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-selector", - "message": "Invalid selector \"> span\"", + "code": "invalid-css-selector", + "message": "Invalid selector", "start": { "line": 5, "column": 2 }, "end": { "line": 5, "column": 8 } } diff --git a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-4/_config.js b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-4/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-4/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-4/errors.json b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-4/errors.json index 78b081586..d7c9cfeb2 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-4/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-combinator-selector-4/errors.json @@ -1,8 +1,8 @@ [ { - "code": "css-invalid-selector", - "message": "Invalid selector \"p >\"", + "code": "invalid-css-selector", + "message": "Invalid selector", "start": { "line": 4, "column": 1 }, - "end": { "line": 4, "column": 4 } + "end": { "line": 4, "column": 5 } } ] diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/errors.json index eb1b20c80..7ba32a53f 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/errors.json @@ -1,6 +1,6 @@ [ { - "code": "css-invalid-global", + "code": "invalid-css-global-placement", "message": ":global(...) can be at the start or end of a selector sequence, but not in the middle", "start": { "line": 2, diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-3/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-placement-3/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-3/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-3/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-placement-3/errors.json index cd5464cca..53e24d8d8 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-3/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-placement-3/errors.json @@ -1,6 +1,6 @@ [ { - "code": "css-invalid-global", + "code": "invalid-css-global-placement", "message": ":global(...) can be at the start or end of a selector sequence, but not in the middle", "start": { "line": 5, diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-4/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-placement-4/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-4/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-4/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-placement-4/errors.json index fbb4d3056..ab791212c 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-4/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-placement-4/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-global-selector-position", - "message": ":global(...) not at the start of a selector sequence should not contain type or universal selectors", + "code": "invalid-css-global-selector-list", + "message": ":global(...) cannot be used to modify a selector, or be modified by another selector", "start": { "line": 2, "column": 5 diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-5/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-placement-5/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-5/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-5/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-placement-5/errors.json index 4b495faff..9099d7adc 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-5/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-placement-5/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-global-selector-position", - "message": ":global(...) not at the start of a selector sequence should not contain type or universal selectors", + "code": "invalid-css-global-selector-list", + "message": ":global(...) cannot be used to modify a selector, or be modified by another selector", "start": { "line": 2, "column": 5 diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-placement/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-placement/errors.json index eb1b20c80..7ba32a53f 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-placement/errors.json @@ -1,6 +1,6 @@ [ { - "code": "css-invalid-global", + "code": "invalid-css-global-placement", "message": ":global(...) can be at the start or end of a selector sequence, but not in the middle", "start": { "line": 2, diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-2/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-selector-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-2/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-selector-2/errors.json index 3049ff71e..36b3ec475 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-2/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-selector-2/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-global-selector", - "message": ":global(...) must contain a single selector", + "code": "invalid-css-global-selector", + "message": ":global(...) must contain exactly one selector", "start": { "line": 11, "column": 5 diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-3/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-selector-3/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-3/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-3/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-selector-3/errors.json index 651acb308..f4a75dcb1 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-3/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-selector-3/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-global-selector", - "message": ":global(...) must contain a single selector", + "code": "invalid-css-global-selector", + "message": ":global(...) must contain exactly one selector", "start": { "line": 5, "column": 5 diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-4/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-selector-4/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-4/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-4/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-selector-4/errors.json index c1bb229de..a6ae2d595 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-4/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-selector-4/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-global-selector", - "message": ":global(...) must contain a single selector", + "code": "invalid-css-global-selector", + "message": ":global(...) must contain exactly one selector", "start": { "line": 2, "column": 5 diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-5/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-selector-5/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-5/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-5/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-selector-5/errors.json index 9efd2be5a..5ac729aa2 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-5/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-selector-5/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-global-selector", - "message": ":global(...) must contain a single selector", + "code": "invalid-css-global-selector", + "message": ":global(...) must contain exactly one selector", "start": { "line": 5, "column": 1 diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/errors.json index 9efd2be5a..5ac729aa2 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-global-selector", - "message": ":global(...) must contain a single selector", + "code": "invalid-css-global-selector", + "message": ":global(...) must contain exactly one selector", "start": { "line": 5, "column": 1 diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/input.svelte b/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/input.svelte index e0b2ffd59..c43adf881 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/input.svelte +++ b/packages/svelte/tests/validator/samples/css-invalid-global-selector-6/input.svelte @@ -1,8 +1,8 @@ <style> - :global(.h1\,h2\,h3).foo { + :global(.h1\,h2\,h3)::foo { color: red; } - :global(h1, h2, h3).foo { + :global(h1, h2, h3)::foo { color: red; } </style> diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector/_config.js b/packages/svelte/tests/validator/samples/css-invalid-global-selector/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-selector/errors.json b/packages/svelte/tests/validator/samples/css-invalid-global-selector/errors.json index c7ef17faf..40bae7d4d 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-selector/errors.json +++ b/packages/svelte/tests/validator/samples/css-invalid-global-selector/errors.json @@ -1,7 +1,7 @@ [ { - "code": "css-invalid-global-selector", - "message": ":global(...) must contain a single selector", + "code": "invalid-css-global-selector", + "message": ":global(...) must contain exactly one selector", "start": { "line": 11, "column": 5 diff --git a/packages/svelte/tests/validator/samples/debug-invalid-args/_config.js b/packages/svelte/tests/validator/samples/debug-invalid-args/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/debug-invalid-args/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/debug-invalid-args/errors.json b/packages/svelte/tests/validator/samples/debug-invalid-args/errors.json index 26c010302..b21af2c43 100644 --- a/packages/svelte/tests/validator/samples/debug-invalid-args/errors.json +++ b/packages/svelte/tests/validator/samples/debug-invalid-args/errors.json @@ -1,6 +1,6 @@ [ { - "code": "invalid-debug-args", + "code": "invalid-debug", "message": "{@debug ...} arguments must be identifiers, not arbitrary expressions", "start": { "line": 2, diff --git a/packages/svelte/tests/validator/samples/default-export-anonymous-class/_config.js b/packages/svelte/tests/validator/samples/default-export-anonymous-class/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/default-export-anonymous-class/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/default-export-anonymous-function/_config.js b/packages/svelte/tests/validator/samples/default-export-anonymous-function/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/default-export-anonymous-function/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/default-export/_config.js b/packages/svelte/tests/validator/samples/default-export/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/default-export/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/dynamic-element-invalid-tag/_config.js b/packages/svelte/tests/validator/samples/dynamic-element-invalid-tag/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/dynamic-element-invalid-tag/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/dynamic-element-invalid-tag/errors.json b/packages/svelte/tests/validator/samples/dynamic-element-invalid-tag/errors.json index 591a1ce54..752d26e1b 100644 --- a/packages/svelte/tests/validator/samples/dynamic-element-invalid-tag/errors.json +++ b/packages/svelte/tests/validator/samples/dynamic-element-invalid-tag/errors.json @@ -1,7 +1,7 @@ [ { - "message": "Invalid element definition", - "code": "invalid-element-definition", + "code": "invalid-svelte-element-definition", + "message": "Invalid element definition — must be an {expression}", "start": { "line": 2, "column": 17 diff --git a/packages/svelte/tests/validator/samples/dynamic-element-missing-tag/_config.js b/packages/svelte/tests/validator/samples/dynamic-element-missing-tag/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/dynamic-element-missing-tag/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/dynamic-element-missing-tag/errors.json b/packages/svelte/tests/validator/samples/dynamic-element-missing-tag/errors.json index 3b2c446d4..58c8af5c6 100644 --- a/packages/svelte/tests/validator/samples/dynamic-element-missing-tag/errors.json +++ b/packages/svelte/tests/validator/samples/dynamic-element-missing-tag/errors.json @@ -1,6 +1,6 @@ [ { - "code": "missing-element-definition", + "code": "missing-svelte-element-definition", "message": "<svelte:element> must have a 'this' attribute", "start": { "line": 2, diff --git a/packages/svelte/tests/validator/samples/each-block-destructured-object-rest-comma-after/_config.js b/packages/svelte/tests/validator/samples/each-block-destructured-object-rest-comma-after/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/each-block-destructured-object-rest-comma-after/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/each-block-destructured-object-rest-comma-after/errors.json b/packages/svelte/tests/validator/samples/each-block-destructured-object-rest-comma-after/errors.json index 6c1d760ea..13c3d150d 100644 --- a/packages/svelte/tests/validator/samples/each-block-destructured-object-rest-comma-after/errors.json +++ b/packages/svelte/tests/validator/samples/each-block-destructured-object-rest-comma-after/errors.json @@ -1,6 +1,6 @@ [ { - "code": "parse-error", + "code": "js-parse-error", "message": "Comma is not permitted after the rest element", "start": { "line": 5, diff --git a/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured-object/_config.js b/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured-object/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured-object/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured-object/errors.json b/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured-object/errors.json index 11428e078..3cb3bbe0d 100644 --- a/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured-object/errors.json +++ b/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured-object/errors.json @@ -1,6 +1,6 @@ [ { - "code": "parse-error", + "code": "js-parse-error", "message": "Unexpected keyword 'case'", "start": { "line": 1, diff --git a/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured/_config.js b/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured/errors.json b/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured/errors.json index 9c80627ea..888bc761a 100644 --- a/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured/errors.json +++ b/packages/svelte/tests/validator/samples/each-block-invalid-context-destructured/errors.json @@ -1,6 +1,6 @@ [ { - "code": "parse-error", + "code": "js-parse-error", "message": "Unexpected token", "start": { "line": 1, diff --git a/packages/svelte/tests/validator/samples/event-attribute/errors.json b/packages/svelte/tests/validator/samples/event-attribute/errors.json new file mode 100644 index 000000000..28a8f9f11 --- /dev/null +++ b/packages/svelte/tests/validator/samples/event-attribute/errors.json @@ -0,0 +1,14 @@ +[ + { + "message": "Event attribute must be a JavaScript expression, not a string", + "code": "invalid-event-attribute-value", + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 21 + } + } +] diff --git a/packages/svelte/tests/validator/samples/event-attribute/input.svelte b/packages/svelte/tests/validator/samples/event-attribute/input.svelte new file mode 100644 index 000000000..2ab0e3982 --- /dev/null +++ b/packages/svelte/tests/validator/samples/event-attribute/input.svelte @@ -0,0 +1,4 @@ +<button onclick={() => {}}>ok</button> +<Button onclick onbar="bar">ok</Button> + +<button onclick="foo">not ok</button> diff --git a/packages/svelte/tests/validator/samples/event-modifiers-invalid-nonpassive/_config.js b/packages/svelte/tests/validator/samples/event-modifiers-invalid-nonpassive/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/event-modifiers-invalid-nonpassive/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/event-modifiers-invalid-nonpassive/errors.json b/packages/svelte/tests/validator/samples/event-modifiers-invalid-nonpassive/errors.json index 08bb48b6d..023095977 100644 --- a/packages/svelte/tests/validator/samples/event-modifiers-invalid-nonpassive/errors.json +++ b/packages/svelte/tests/validator/samples/event-modifiers-invalid-nonpassive/errors.json @@ -1,7 +1,7 @@ [ { "message": "The 'passive' and 'nonpassive' modifiers cannot be used together", - "code": "invalid-event-modifier", + "code": "invalid-event-modifier-combination", "start": { "line": 1, "column": 5 diff --git a/packages/svelte/tests/validator/samples/event-modifiers-invalid-passive/_config.js b/packages/svelte/tests/validator/samples/event-modifiers-invalid-passive/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/event-modifiers-invalid-passive/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/event-modifiers-invalid-passive/errors.json b/packages/svelte/tests/validator/samples/event-modifiers-invalid-passive/errors.json index 542e50738..db8289791 100644 --- a/packages/svelte/tests/validator/samples/event-modifiers-invalid-passive/errors.json +++ b/packages/svelte/tests/validator/samples/event-modifiers-invalid-passive/errors.json @@ -1,7 +1,7 @@ [ { "message": "The 'passive' and 'preventDefault' modifiers cannot be used together", - "code": "invalid-event-modifier", + "code": "invalid-event-modifier-combination", "start": { "line": 1, "column": 5 diff --git a/packages/svelte/tests/validator/samples/event-modifiers-invalid/_config.js b/packages/svelte/tests/validator/samples/event-modifiers-invalid/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/event-modifiers-invalid/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/event-modifiers-legacy/errors.json b/packages/svelte/tests/validator/samples/event-modifiers-legacy/errors.json deleted file mode 100644 index 735e7c21f..000000000 --- a/packages/svelte/tests/validator/samples/event-modifiers-legacy/errors.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "message": "The 'once' modifier cannot be used in legacy mode", - "code": "invalid-event-modifier", - "start": { - "line": 1, - "column": 8 - }, - "end": { - "line": 1, - "column": 37 - } - } -] diff --git a/packages/svelte/tests/validator/samples/event-modifiers-legacy/input.svelte b/packages/svelte/tests/validator/samples/event-modifiers-legacy/input.svelte deleted file mode 100644 index c53a616fb..000000000 --- a/packages/svelte/tests/validator/samples/event-modifiers-legacy/input.svelte +++ /dev/null @@ -1 +0,0 @@ -<button on:click|once="{handleClick}"></button> \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/html-block-in-attribute/_config.js b/packages/svelte/tests/validator/samples/html-block-in-attribute/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/html-block-in-attribute/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/html-block-in-attribute/errors.json b/packages/svelte/tests/validator/samples/html-block-in-attribute/errors.json index 3e73629dc..6c7237f03 100644 --- a/packages/svelte/tests/validator/samples/html-block-in-attribute/errors.json +++ b/packages/svelte/tests/validator/samples/html-block-in-attribute/errors.json @@ -1,7 +1,7 @@ [ { "code": "invalid-tag-placement", - "message": "{@html} tag cannot be in attribute value", + "message": "{@html ...} tag cannot be in attribute value", "start": { "line": 1, "column": 12 }, "end": { "line": 1, "column": 12 } } diff --git a/packages/svelte/tests/validator/samples/html-block-in-textarea/_config.js b/packages/svelte/tests/validator/samples/html-block-in-textarea/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/html-block-in-textarea/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/html-block-in-textarea/errors.json b/packages/svelte/tests/validator/samples/html-block-in-textarea/errors.json index ea01211d4..8d98066d5 100644 --- a/packages/svelte/tests/validator/samples/html-block-in-textarea/errors.json +++ b/packages/svelte/tests/validator/samples/html-block-in-textarea/errors.json @@ -1,7 +1,7 @@ [ { "code": "invalid-tag-placement", - "message": "{@html} tag cannot be inside <textarea>", + "message": "{@html ...} tag cannot be inside <textarea>", "start": { "line": 2, "column": 1 }, "end": { "line": 2, "column": 1 } } diff --git a/packages/svelte/tests/validator/samples/inline-new-class-2/input.svelte b/packages/svelte/tests/validator/samples/inline-new-class-2/input.svelte new file mode 100644 index 000000000..34d31cd74 --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class-2/input.svelte @@ -0,0 +1,8 @@ +<script> + function bar() { + class Foo { + foo = $state(0) + } + const a = new Foo(); + } +</script> diff --git a/packages/svelte/tests/validator/samples/inline-new-class-2/warnings.json b/packages/svelte/tests/validator/samples/inline-new-class-2/warnings.json new file mode 100644 index 000000000..695631437 --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class-2/warnings.json @@ -0,0 +1,14 @@ +[ + { + "code": "avoid-nested-class", + "message": "Avoid declaring classes below the top level scope", + "start": { + "column": 2, + "line": 3 + }, + "end": { + "column": 3, + "line": 5 + } + } +] diff --git a/packages/svelte/tests/validator/samples/inline-new-class-3/input.svelte b/packages/svelte/tests/validator/samples/inline-new-class-3/input.svelte new file mode 100644 index 000000000..1dd632df0 --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class-3/input.svelte @@ -0,0 +1,8 @@ +<script> + class Foo { + foo = $state(0) + } + function bar() { + const a = new Foo(); + } +</script> diff --git a/packages/svelte/tests/validator/samples/inline-new-class-3/warnings.json b/packages/svelte/tests/validator/samples/inline-new-class-3/warnings.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class-3/warnings.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/inline-new-class-4/input.svelte b/packages/svelte/tests/validator/samples/inline-new-class-4/input.svelte new file mode 100644 index 000000000..1f8a6f5ba --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class-4/input.svelte @@ -0,0 +1,7 @@ +<script> + function bar() { + const a = new class Foo { + foo = $state(0) + } + } +</script> diff --git a/packages/svelte/tests/validator/samples/inline-new-class-4/warnings.json b/packages/svelte/tests/validator/samples/inline-new-class-4/warnings.json new file mode 100644 index 000000000..71beba537 --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class-4/warnings.json @@ -0,0 +1,14 @@ +[ + { + "code": "avoid-inline-class", + "message": "Avoid 'new class' — instead, declare the class at the top level scope", + "start": { + "column": 12, + "line": 3 + }, + "end": { + "column": 3, + "line": 5 + } + } +] diff --git a/packages/svelte/tests/validator/samples/inline-new-class-5/input.svelte b/packages/svelte/tests/validator/samples/inline-new-class-5/input.svelte new file mode 100644 index 000000000..817bb2931 --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class-5/input.svelte @@ -0,0 +1,5 @@ +<script context="module"> + const a = new class Foo { + foo = $state(0) + } +</script> diff --git a/packages/svelte/tests/validator/samples/inline-new-class-5/warnings.json b/packages/svelte/tests/validator/samples/inline-new-class-5/warnings.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class-5/warnings.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/inline-new-class/input.svelte b/packages/svelte/tests/validator/samples/inline-new-class/input.svelte new file mode 100644 index 000000000..10a29724f --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class/input.svelte @@ -0,0 +1,5 @@ +<script> + const a = new class { + foo = $state(0) + } +</script> diff --git a/packages/svelte/tests/validator/samples/inline-new-class/warnings.json b/packages/svelte/tests/validator/samples/inline-new-class/warnings.json new file mode 100644 index 000000000..6995428be --- /dev/null +++ b/packages/svelte/tests/validator/samples/inline-new-class/warnings.json @@ -0,0 +1,14 @@ +[ + { + "code": "avoid-inline-class", + "message": "Avoid 'new class' — instead, declare the class at the top level scope", + "start": { + "column": 11, + "line": 2 + }, + "end": { + "column": 2, + "line": 4 + } + } +] diff --git a/packages/svelte/tests/validator/samples/let-directive/errors.json b/packages/svelte/tests/validator/samples/let-directive/errors.json new file mode 100644 index 000000000..bb239bafa --- /dev/null +++ b/packages/svelte/tests/validator/samples/let-directive/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "invalid-let-directive-placement", + "message": "let directive at invalid position", + "start": { "line": 6, "column": 15 }, + "end": { "line": 6, "column": 20 } + } +] diff --git a/packages/svelte/tests/validator/samples/let-directive/input.svelte b/packages/svelte/tests/validator/samples/let-directive/input.svelte new file mode 100644 index 000000000..90763028b --- /dev/null +++ b/packages/svelte/tests/validator/samples/let-directive/input.svelte @@ -0,0 +1,6 @@ +{#if foo} + <svelte:self let:x></svelte:self> +{/if} +<Component let:x></Component> +<div let:x></div><!-- strictly speaking this is invalid, but it was never checked in Svelte 3/4 either, and is now deprecated, so don't mind --> +<svelte:window let:x></svelte:window> \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/logic-block-in-attribute/_config.js b/packages/svelte/tests/validator/samples/logic-block-in-attribute/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/logic-block-in-attribute/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/logic-block-in-attribute/errors.json b/packages/svelte/tests/validator/samples/logic-block-in-attribute/errors.json index 43c0fb941..883fc0e47 100644 --- a/packages/svelte/tests/validator/samples/logic-block-in-attribute/errors.json +++ b/packages/svelte/tests/validator/samples/logic-block-in-attribute/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-logic-block-placement", - "message": "{#if} logic block cannot be in attribute value", + "code": "invalid-block-placement", + "message": "{#if ...} block cannot be in attribute value", "start": { "line": 1, "column": 12 }, "end": { "line": 1, "column": 12 } } diff --git a/packages/svelte/tests/validator/samples/logic-block-in-textarea/_config.js b/packages/svelte/tests/validator/samples/logic-block-in-textarea/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/logic-block-in-textarea/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/logic-block-in-textarea/errors.json b/packages/svelte/tests/validator/samples/logic-block-in-textarea/errors.json index d00708908..a371f9b76 100644 --- a/packages/svelte/tests/validator/samples/logic-block-in-textarea/errors.json +++ b/packages/svelte/tests/validator/samples/logic-block-in-textarea/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-logic-block-placement", - "message": "{#each} logic block cannot be inside <textarea>", + "code": "invalid-block-placement", + "message": "{#each ...} block cannot be inside <textarea>", "start": { "line": 2, "column": 1 }, "end": { "line": 2, "column": 1 } } diff --git a/packages/svelte/tests/validator/samples/module-script-reactive-declaration/_config.js b/packages/svelte/tests/validator/samples/module-script-reactive-declaration/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/module-script-reactive-declaration/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/module-script-reactive-declaration/warnings.json b/packages/svelte/tests/validator/samples/module-script-reactive-declaration/warnings.json index 81d5bc966..df53ed612 100644 --- a/packages/svelte/tests/validator/samples/module-script-reactive-declaration/warnings.json +++ b/packages/svelte/tests/validator/samples/module-script-reactive-declaration/warnings.json @@ -1,7 +1,7 @@ [ { - "message": "$: has no effect in a module script", - "code": "module-script-reactive-declaration", + "message": "Reactive declarations only exist at the top level of the instance script", + "code": "no-reactive-declaration", "start": { "line": 4, "column": 1 }, "end": { "line": 4, "column": 23 } } diff --git a/packages/svelte/tests/validator/samples/multiple-script-default-context/_config.js b/packages/svelte/tests/validator/samples/multiple-script-default-context/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/multiple-script-default-context/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/multiple-script-default-context/errors.json b/packages/svelte/tests/validator/samples/multiple-script-default-context/errors.json index ef79afebd..58eb09292 100644 --- a/packages/svelte/tests/validator/samples/multiple-script-default-context/errors.json +++ b/packages/svelte/tests/validator/samples/multiple-script-default-context/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-script", - "message": "A component can only have one instance-level <script> element", + "code": "duplicate-script-element", + "message": "A component can have a single top-level <script> element and/or a single top-level <script context=\"module\"> element", "start": { "line": 5, "column": 0 diff --git a/packages/svelte/tests/validator/samples/multiple-script-module-context/_config.js b/packages/svelte/tests/validator/samples/multiple-script-module-context/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/multiple-script-module-context/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/multiple-script-module-context/errors.json b/packages/svelte/tests/validator/samples/multiple-script-module-context/errors.json index 5dc7a90a7..58eb09292 100644 --- a/packages/svelte/tests/validator/samples/multiple-script-module-context/errors.json +++ b/packages/svelte/tests/validator/samples/multiple-script-module-context/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-script", - "message": "A component can only have one <script context=\"module\"> element", + "code": "duplicate-script-element", + "message": "A component can have a single top-level <script> element and/or a single top-level <script context=\"module\"> element", "start": { "line": 5, "column": 0 diff --git a/packages/svelte/tests/validator/samples/namespace-invalid-unguessable/_config.js b/packages/svelte/tests/validator/samples/namespace-invalid-unguessable/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/namespace-invalid-unguessable/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/namespace-invalid-unguessable/errors.json b/packages/svelte/tests/validator/samples/namespace-invalid-unguessable/errors.json deleted file mode 100644 index 880109e32..000000000 --- a/packages/svelte/tests/validator/samples/namespace-invalid-unguessable/errors.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "code": "invalid-namespace-property", - "message": "Invalid namespace 'lol'", - "start": { - "line": 1, - "column": 16 - }, - "end": { - "line": 1, - "column": 31 - } - } -] diff --git a/packages/svelte/tests/validator/samples/namespace-invalid-unguessable/input.svelte b/packages/svelte/tests/validator/samples/namespace-invalid-unguessable/input.svelte deleted file mode 100644 index fdbd779b5..000000000 --- a/packages/svelte/tests/validator/samples/namespace-invalid-unguessable/input.svelte +++ /dev/null @@ -1 +0,0 @@ -<svelte:options namespace="lol"/> diff --git a/packages/svelte/tests/validator/samples/namespace-invalid/_config.js b/packages/svelte/tests/validator/samples/namespace-invalid/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/namespace-invalid/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/namespace-invalid/errors.json b/packages/svelte/tests/validator/samples/namespace-invalid/errors.json index acab9d3fa..7081b9da2 100644 --- a/packages/svelte/tests/validator/samples/namespace-invalid/errors.json +++ b/packages/svelte/tests/validator/samples/namespace-invalid/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-namespace-property", - "message": "Invalid namespace 'http://www.w3.org/1999/svg' (did you mean 'http://www.w3.org/2000/svg'?)", + "code": "invalid-svelte-option-namespace", + "message": "Unsupported <svelte:option> value for \"namespace\". Valid values are \"html\", \"svg\" or \"foreign\".", "start": { "line": 1, "column": 16 diff --git a/packages/svelte/tests/validator/samples/namespace-non-literal/_config.js b/packages/svelte/tests/validator/samples/namespace-non-literal/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/namespace-non-literal/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/namespace-non-literal/errors.json b/packages/svelte/tests/validator/samples/namespace-non-literal/errors.json index 697e9e932..30c7ea279 100644 --- a/packages/svelte/tests/validator/samples/namespace-non-literal/errors.json +++ b/packages/svelte/tests/validator/samples/namespace-non-literal/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-namespace-attribute", - "message": "The 'namespace' attribute must be a string literal representing a valid namespace", + "code": "invalid-svelte-option-namespace", + "message": "Unsupported <svelte:option> value for \"namespace\". Valid values are \"html\", \"svg\" or \"foreign\".", "start": { "line": 1, "column": 16 diff --git a/packages/svelte/tests/validator/samples/event-modifiers-legacy/_config.js b/packages/svelte/tests/validator/samples/no-runes-mode/_config.js similarity index 73% rename from packages/svelte/tests/validator/samples/event-modifiers-legacy/_config.js rename to packages/svelte/tests/validator/samples/no-runes-mode/_config.js index 6e402c327..477cb2b13 100644 --- a/packages/svelte/tests/validator/samples/event-modifiers-legacy/_config.js +++ b/packages/svelte/tests/validator/samples/no-runes-mode/_config.js @@ -1,8 +1,7 @@ import { test } from '../../test'; export default test({ - skip: true, compileOptions: { - // legacy: true + runes: false } }); diff --git a/packages/svelte/tests/validator/samples/no-runes-mode/input.svelte b/packages/svelte/tests/validator/samples/no-runes-mode/input.svelte new file mode 100644 index 000000000..daa1686fa --- /dev/null +++ b/packages/svelte/tests/validator/samples/no-runes-mode/input.svelte @@ -0,0 +1,6 @@ +<script> + import { state } from './store'; + const x = $state(); +</script> + +{x} diff --git a/packages/svelte/tests/validator/samples/reactive-declaration-non-top-level/_config.js b/packages/svelte/tests/validator/samples/reactive-declaration-non-top-level/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/reactive-declaration-non-top-level/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/reactive-declaration-non-top-level/warnings.json b/packages/svelte/tests/validator/samples/reactive-declaration-non-top-level/warnings.json index f677ecb28..f7598524e 100644 --- a/packages/svelte/tests/validator/samples/reactive-declaration-non-top-level/warnings.json +++ b/packages/svelte/tests/validator/samples/reactive-declaration-non-top-level/warnings.json @@ -1,7 +1,7 @@ [ { - "message": "$: has no effect outside of the top-level", - "code": "non-top-level-reactive-declaration", + "message": "Reactive declarations only exist at the top level of the instance script", + "code": "no-reactive-declaration", "start": { "line": 6, "column": 2 }, "end": { "line": 6, "column": 22 } } diff --git a/packages/svelte/tests/validator/samples/rest-eachblock-binding-2/_config.js b/packages/svelte/tests/validator/samples/rest-eachblock-binding-2/_config.js index 64fdc120d..7fc1f9f61 100644 --- a/packages/svelte/tests/validator/samples/rest-eachblock-binding-2/_config.js +++ b/packages/svelte/tests/validator/samples/rest-eachblock-binding-2/_config.js @@ -1,3 +1,4 @@ import { test } from '../../test'; +// TODO this likely works in the new world - remove this warning? export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/rest-eachblock-binding-3/_config.js b/packages/svelte/tests/validator/samples/rest-eachblock-binding-3/_config.js index 64fdc120d..7fc1f9f61 100644 --- a/packages/svelte/tests/validator/samples/rest-eachblock-binding-3/_config.js +++ b/packages/svelte/tests/validator/samples/rest-eachblock-binding-3/_config.js @@ -1,3 +1,4 @@ import { test } from '../../test'; +// TODO this likely works in the new world - remove this warning? export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/rest-eachblock-binding-nested-rest/_config.js b/packages/svelte/tests/validator/samples/rest-eachblock-binding-nested-rest/_config.js index 64fdc120d..a7d521e51 100644 --- a/packages/svelte/tests/validator/samples/rest-eachblock-binding-nested-rest/_config.js +++ b/packages/svelte/tests/validator/samples/rest-eachblock-binding-nested-rest/_config.js @@ -1,3 +1,4 @@ import { test } from '../../test'; +// TODO this maybe works in the new world - remove this warning? export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/rest-eachblock-binding/_config.js b/packages/svelte/tests/validator/samples/rest-eachblock-binding/_config.js index 64fdc120d..7fc1f9f61 100644 --- a/packages/svelte/tests/validator/samples/rest-eachblock-binding/_config.js +++ b/packages/svelte/tests/validator/samples/rest-eachblock-binding/_config.js @@ -1,3 +1,4 @@ import { test } from '../../test'; +// TODO this likely works in the new world - remove this warning? export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/runes-referenced-nonstate/_config.js b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/_config.js new file mode 100644 index 000000000..f47bee71d --- /dev/null +++ b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte new file mode 100644 index 000000000..fd9d6c317 --- /dev/null +++ b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/input.svelte @@ -0,0 +1,10 @@ +<script> + let a = $state(1); + let b = 2; + let c = 3; +</script> + +<button onclick={() => a += 1}>a += 1</button> +<button onclick={() => b += 1}>b += 1</button> +<button onclick={() => c += 1}>c += 1</button> +<p>{a} + {b} + {c} = {a + b + c}</p> diff --git a/packages/svelte/tests/validator/samples/runes-referenced-nonstate/warnings.json b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/warnings.json new file mode 100644 index 000000000..7e251a4e7 --- /dev/null +++ b/packages/svelte/tests/validator/samples/runes-referenced-nonstate/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "non-state-reference", + "message": "b is updated, but is not declared with $state(...). Changing its value will not correctly trigger updates.", + "start": { + "column": 5, + "line": 3 + }, + "end": { + "column": 6, + "line": 3 + } + }, + { + "code": "non-state-reference", + "message": "c is updated, but is not declared with $state(...). Changing its value will not correctly trigger updates.", + "start": { + "column": 5, + "line": 4 + }, + "end": { + "column": 6, + "line": 4 + } + } +] diff --git a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/input.svelte b/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/input.svelte deleted file mode 100644 index eb4e709f4..000000000 --- a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/input.svelte +++ /dev/null @@ -1,10 +0,0 @@ -<script> - function createCounter() { - let count = $state(0); - return { - get count() { - return count - } - }; - } -</script> diff --git a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/warnings.json b/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/warnings.json deleted file mode 100644 index 628f1f2e9..000000000 --- a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/warnings.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "code": "state-rune-not-mutated", - "end": { - "column": 11, - "line": 3 - }, - "message": "count is declared with $state(...) but is never updated. Did you mean to create a function that changes its value?", - "start": { - "column": 6, - "line": 3 - } - } -] diff --git a/packages/svelte/tests/validator/samples/script-invalid-context/_config.js b/packages/svelte/tests/validator/samples/script-invalid-context/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/script-invalid-context/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/script-invalid-context/errors.json b/packages/svelte/tests/validator/samples/script-invalid-context/errors.json index 7046bd50e..d7a502a2f 100644 --- a/packages/svelte/tests/validator/samples/script-invalid-context/errors.json +++ b/packages/svelte/tests/validator/samples/script-invalid-context/errors.json @@ -1,6 +1,6 @@ [ { - "code": "invalid-script", + "code": "invalid-script-context", "message": "If the context attribute is supplied, its value must be \"module\"", "start": { "line": 1, diff --git a/packages/svelte/tests/validator/samples/security-anchor-rel-noreferer-legacy/input.svelte b/packages/svelte/tests/validator/samples/security-anchor-rel-noreferer-legacy/input.svelte deleted file mode 100644 index f5361e5cf..000000000 --- a/packages/svelte/tests/validator/samples/security-anchor-rel-noreferer-legacy/input.svelte +++ /dev/null @@ -1,33 +0,0 @@ -<a href="https://svelte.dev" target="_blank">svelte website (invalid)</a> -<a href="https://svelte.dev" target="_blank" rel="">svelte website (invalid)</a> -<a href="https://svelte.dev" target="_blank" rel="noopener">svelte website (invalid)</a> -<a href={'https://svelte.dev'} target="_blank">svelte website (invalid)</a> -<a href={'https://svelte.dev'} target="_blank" rel="">svelte website (invalid)</a> -<a href={'https://svelte.dev'} target="_blank" rel="noopener">svelte website (invalid)</a> -<a href="//svelte.dev" target="_blank">svelte website (invalid)</a> -<a href="//svelte.dev" target="_blank" rel="">svelte website (invalid)</a> -<a href="//svelte.dev" target="_blank" rel="noopener">svelte website (invalid)</a> -<a href="http://svelte.dev" target="_blank">svelte website (invalid)</a> -<a href="http://svelte.dev" target="_blank" rel="">svelte website (invalid)</a> -<a href="http://svelte.dev" target="_blank" rel="noopener">svelte website (invalid)</a> -<a href="HTTP://svelte.dev" target="_blank">svelte website (invalid)</a> -<a href="HTTP://svelte.dev" target="_blank" rel="">svelte website (invalid)</a> -<a href="HTTP://svelte.dev" target="_blank" rel="noopener">svelte website (invalid)</a> -<a href={'HTTPS://svelte.dev'} target="_blank">svelte website (invalid)</a> -<a href={'HTTPS://svelte.dev'} target="_blank" rel="">svelte website (invalid)</a> -<a href={'HTTPS://svelte.dev'} target="_blank" rel="noopener">svelte website (invalid)</a> -<a href="same-host" target="_blank">Same host (valid)</a> -<a href="same-host" target="_blank" rel="">Same host (valid)</a> -<a href="same-host" target="_blank" rel="noopener">Same host (valid)</a> -<a href="http://svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a> -<a href="http://svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a> -<a href="HTTP://svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a> -<a href="HTTP://svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a> -<a href="https://svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a> -<a href="https://svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a> -<a href="HTTPS://svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a> -<a href="HTTPS://svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a> -<a href="//svelte.dev" target="_blank" rel="noreferrer">svelte website (valid)</a> -<a href="//svelte.dev" target="_blank" rel="noreferrer noopener">svelte website (valid)</a> -<!-- dynamic rel value should not warn--> -<a href="//svelte.dev" target="_blank" rel={`${Math.random()}`}>svelte website (valid)</a> diff --git a/packages/svelte/tests/validator/samples/security-anchor-rel-noreferer-legacy/warnings.json b/packages/svelte/tests/validator/samples/security-anchor-rel-noreferer-legacy/warnings.json deleted file mode 100644 index 2986f137e..000000000 --- a/packages/svelte/tests/validator/samples/security-anchor-rel-noreferer-legacy/warnings.json +++ /dev/null @@ -1,218 +0,0 @@ -[ - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 73, - "line": 1 - }, - "start": { - "column": 0, - "line": 1 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 80, - "line": 2 - }, - "start": { - "column": 0, - "line": 2 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 88, - "line": 3 - }, - "start": { - "column": 0, - "line": 3 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 75, - "line": 4 - }, - "start": { - "column": 0, - "line": 4 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 82, - "line": 5 - }, - "start": { - "column": 0, - "line": 5 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 90, - "line": 6 - }, - "start": { - "column": 0, - "line": 6 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 67, - "line": 7 - }, - "start": { - "column": 0, - "line": 7 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 74, - "line": 8 - }, - "start": { - "column": 0, - "line": 8 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 82, - "line": 9 - }, - "start": { - "column": 0, - "line": 9 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 72, - "line": 10 - }, - "start": { - "column": 0, - "line": 10 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 79, - "line": 11 - }, - "start": { - "column": 0, - "line": 11 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 87, - "line": 12 - }, - "start": { - "column": 0, - "line": 12 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 72, - "line": 13 - }, - "start": { - "column": 0, - "line": 13 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 79, - "line": 14 - }, - "start": { - "column": 0, - "line": 14 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 87, - "line": 15 - }, - "start": { - "column": 0, - "line": 15 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 75, - "line": 16 - }, - "start": { - "column": 0, - "line": 16 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 82, - "line": 17 - }, - "start": { - "column": 0, - "line": 17 - } - }, - { - "code": "security-anchor-rel-noreferrer", - "message": "Security: Anchor with \"target=_blank\" should have rel attribute containing the value \"noreferrer\"", - "end": { - "column": 90, - "line": 18 - }, - "start": { - "column": 0, - "line": 18 - } - } -] diff --git a/packages/svelte/tests/validator/samples/slot-attribute-invalid/_config.js b/packages/svelte/tests/validator/samples/slot-attribute-invalid/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/slot-attribute-invalid/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/slot-attribute-invalid/errors.json b/packages/svelte/tests/validator/samples/slot-attribute-invalid/errors.json index 643e96359..a5a385578 100644 --- a/packages/svelte/tests/validator/samples/slot-attribute-invalid/errors.json +++ b/packages/svelte/tests/validator/samples/slot-attribute-invalid/errors.json @@ -1,6 +1,6 @@ [ { - "code": "invalid-slotted-content", + "code": "invalid-slot-placement", "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element", "start": { "line": 1, diff --git a/packages/svelte/tests/validator/samples/slot-warning/warnings.json b/packages/svelte/tests/validator/samples/slot-warning/warnings.json index 66abd72fb..57440d2d7 100644 --- a/packages/svelte/tests/validator/samples/slot-warning/warnings.json +++ b/packages/svelte/tests/validator/samples/slot-warning/warnings.json @@ -1,7 +1,7 @@ [ { "code": "a11y-click-events-have-key-events", - "message": "A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", "start": { "column": 1, "line": 7 diff --git a/packages/svelte/tests/validator/samples/slot-warning2/warnings.json b/packages/svelte/tests/validator/samples/slot-warning2/warnings.json index 921199508..7dff2657d 100644 --- a/packages/svelte/tests/validator/samples/slot-warning2/warnings.json +++ b/packages/svelte/tests/validator/samples/slot-warning2/warnings.json @@ -1,7 +1,7 @@ [ { "code": "a11y-click-events-have-key-events", - "message": "A11y: visible, non-interactive elements with an on:click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", + "message": "A11y: visible, non-interactive elements with a click event must be accompanied by a keyboard event handler. Consider whether an interactive element such as <button type=\"button\"> or <a> might be more appropriate. See https://svelte.dev/docs/accessibility-warnings#a11y-click-events-have-key-events for more details.", "start": { "column": 1, "line": 8 diff --git a/packages/svelte/tests/validator/samples/store-looking-like-rune/input.svelte b/packages/svelte/tests/validator/samples/store-looking-like-rune/input.svelte new file mode 100644 index 000000000..d5d0f7694 --- /dev/null +++ b/packages/svelte/tests/validator/samples/store-looking-like-rune/input.svelte @@ -0,0 +1,8 @@ +<script> + import { writable } from 'svelte/store'; + + export let initial; + const state = writable(initial); +</script> + +<div>{$state}</div> diff --git a/packages/svelte/tests/validator/samples/store-looking-like-rune/warnings.json b/packages/svelte/tests/validator/samples/store-looking-like-rune/warnings.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/svelte/tests/validator/samples/store-looking-like-rune/warnings.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/store-runes-conflict/warnings.json b/packages/svelte/tests/validator/samples/store-runes-conflict/warnings.json index 534a5e198..58236d814 100644 --- a/packages/svelte/tests/validator/samples/store-runes-conflict/warnings.json +++ b/packages/svelte/tests/validator/samples/store-runes-conflict/warnings.json @@ -3,12 +3,12 @@ "code": "store-with-rune-name", "message": "It looks like you're using the $state rune, but there is a local binding called state. Referencing a local variable with a $ prefix will create a store subscription. Please rename state to avoid the ambiguity.", "start": { - "column": 7, - "line": 2 + "column": 1, + "line": 3 }, "end": { - "column": 12, - "line": 2 + "column": 7, + "line": 3 } } ] diff --git a/packages/svelte/tests/validator/samples/svelte-fragment-placement-2/_config.js b/packages/svelte/tests/validator/samples/svelte-fragment-placement-2/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/svelte-fragment-placement-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/svelte-fragment-placement-2/errors.json b/packages/svelte/tests/validator/samples/svelte-fragment-placement-2/errors.json index 871e5f5de..955d756a6 100644 --- a/packages/svelte/tests/validator/samples/svelte-fragment-placement-2/errors.json +++ b/packages/svelte/tests/validator/samples/svelte-fragment-placement-2/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-slotted-content", - "message": "<svelte:fragment> must be a child of a component", + "code": "invalid-svelte-fragment-placement", + "message": "<svelte:fragment> must be the direct child of a component", "start": { "line": 5, "column": 0 }, "end": { "line": 7, "column": 18 } } diff --git a/packages/svelte/tests/validator/samples/svelte-fragment-placement/_config.js b/packages/svelte/tests/validator/samples/svelte-fragment-placement/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/svelte-fragment-placement/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/svelte-fragment-placement/errors.json b/packages/svelte/tests/validator/samples/svelte-fragment-placement/errors.json index 663595320..2ad4c8390 100644 --- a/packages/svelte/tests/validator/samples/svelte-fragment-placement/errors.json +++ b/packages/svelte/tests/validator/samples/svelte-fragment-placement/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-slotted-content", - "message": "<svelte:fragment> must be a child of a component", + "code": "invalid-svelte-fragment-placement", + "message": "<svelte:fragment> must be the direct child of a component", "start": { "line": 7, "column": 2 }, "end": { "line": 9, "column": 20 } } diff --git a/packages/svelte/tests/validator/samples/svelte-head-attributes/errors.json b/packages/svelte/tests/validator/samples/svelte-head-attributes/errors.json new file mode 100644 index 000000000..026ba121a --- /dev/null +++ b/packages/svelte/tests/validator/samples/svelte-head-attributes/errors.json @@ -0,0 +1,8 @@ +[ + { + "code": "illegal-svelte-head-attribute", + "message": "<svelte:head> cannot have attributes nor directives", + "start": { "line": 1, "column": 13 }, + "end": { "line": 1, "column": 14 } + } +] diff --git a/packages/svelte/tests/validator/samples/svelte-head-attributes/input.svelte b/packages/svelte/tests/validator/samples/svelte-head-attributes/input.svelte new file mode 100644 index 000000000..948ea0435 --- /dev/null +++ b/packages/svelte/tests/validator/samples/svelte-head-attributes/input.svelte @@ -0,0 +1 @@ +<svelte:head x></svelte:head> \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/textarea-value-children/_config.js b/packages/svelte/tests/validator/samples/textarea-value-children/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/textarea-value-children/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/textarea-value-children/errors.json b/packages/svelte/tests/validator/samples/textarea-value-children/errors.json index 4c9e7e853..c4aed6b42 100644 --- a/packages/svelte/tests/validator/samples/textarea-value-children/errors.json +++ b/packages/svelte/tests/validator/samples/textarea-value-children/errors.json @@ -1,14 +1,14 @@ [ { - "code": "textarea-duplicate-value", + "code": "invalid-textarea-content", "message": "A <textarea> can have either a value attribute or (equivalently) child content, but not both", "start": { "line": 1, - "column": 10 + "column": 0 }, "end": { - "line": 1, - "column": 23 + "line": 3, + "column": 11 } } ] diff --git a/packages/svelte/tests/validator/samples/title-no-attributes/_config.js b/packages/svelte/tests/validator/samples/title-no-attributes/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/title-no-attributes/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/title-no-attributes/errors.json b/packages/svelte/tests/validator/samples/title-no-attributes/errors.json index a58598d50..3f0410a6b 100644 --- a/packages/svelte/tests/validator/samples/title-no-attributes/errors.json +++ b/packages/svelte/tests/validator/samples/title-no-attributes/errors.json @@ -1,7 +1,7 @@ [ { - "code": "illegal-attribute", - "message": "<title> cannot have attributes", + "code": "illegal-title-attribute", + "message": "<title> cannot have attributes nor directives", "start": { "line": 2, "column": 8 diff --git a/packages/svelte/tests/validator/samples/title-no-children/_config.js b/packages/svelte/tests/validator/samples/title-no-children/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/title-no-children/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/title-no-children/errors.json b/packages/svelte/tests/validator/samples/title-no-children/errors.json index fa1c0fdce..9d7a7667a 100644 --- a/packages/svelte/tests/validator/samples/title-no-children/errors.json +++ b/packages/svelte/tests/validator/samples/title-no-children/errors.json @@ -1,6 +1,6 @@ [ { - "code": "illegal-structure", + "code": "invalid-title-content", "message": "<title> can only contain text and {tags}", "start": { "line": 2, diff --git a/packages/svelte/tests/validator/samples/transition-duplicate-in-transition/_config.js b/packages/svelte/tests/validator/samples/transition-duplicate-in-transition/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/transition-duplicate-in-transition/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/transition-duplicate-in/_config.js b/packages/svelte/tests/validator/samples/transition-duplicate-in/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/transition-duplicate-in/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/transition-duplicate-out-transition/_config.js b/packages/svelte/tests/validator/samples/transition-duplicate-out-transition/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/transition-duplicate-out-transition/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/transition-duplicate-out/_config.js b/packages/svelte/tests/validator/samples/transition-duplicate-out/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/transition-duplicate-out/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/transition-duplicate-transition-in/_config.js b/packages/svelte/tests/validator/samples/transition-duplicate-transition-in/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/transition-duplicate-transition-in/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/transition-duplicate-transition-out/_config.js b/packages/svelte/tests/validator/samples/transition-duplicate-transition-out/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/transition-duplicate-transition-out/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/transition-duplicate-transition/_config.js b/packages/svelte/tests/validator/samples/transition-duplicate-transition/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/transition-duplicate-transition/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/transition-on-component/_config.js b/packages/svelte/tests/validator/samples/transition-on-component/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/transition-on-component/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/transition-on-component/errors.json b/packages/svelte/tests/validator/samples/transition-on-component/errors.json index 0a08dd329..7b2403bee 100644 --- a/packages/svelte/tests/validator/samples/transition-on-component/errors.json +++ b/packages/svelte/tests/validator/samples/transition-on-component/errors.json @@ -1,7 +1,7 @@ [ { - "code": "invalid-transition", - "message": "Transitions can only be applied to DOM elements, not components", + "code": "invalid-component-directive", + "message": "This type of directive is not valid on components", "start": { "line": 7, "column": 8 diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/_config.js b/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/errors.json b/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/errors.json index 0baccb2b7..7f7a7eb6f 100644 --- a/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/errors.json +++ b/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/errors.json @@ -1,13 +1,13 @@ [ { "code": "invalid-binding", - "message": "'innerwidth' is not a valid binding on <svelte:window> (did you mean 'innerWidth'?)", + "message": "'innerwidth' is not a valid binding (did you mean 'innerWidth'?)", "start": { - "line": 1, + "line": 5, "column": 15 }, "end": { - "line": 1, + "line": 5, "column": 34 } } diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/input.svelte b/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/input.svelte index 72b5e36cf..03824cbd3 100644 --- a/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/input.svelte +++ b/packages/svelte/tests/validator/samples/window-binding-invalid-innerwidth/input.svelte @@ -1 +1,5 @@ -<svelte:window bind:innerwidth={w}/> \ No newline at end of file +<script> + let w; +</script> + +<svelte:window bind:innerwidth={w}/> diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid-value/_config.js b/packages/svelte/tests/validator/samples/window-binding-invalid-value/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/window-binding-invalid-value/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid-value/errors.json b/packages/svelte/tests/validator/samples/window-binding-invalid-value/errors.json deleted file mode 100644 index a0029bef4..000000000 --- a/packages/svelte/tests/validator/samples/window-binding-invalid-value/errors.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "code": "invalid-binding", - "message": "Bindings on <svelte:window> must be to top-level properties, e.g. 'baz' rather than 'foo.bar.baz'", - "start": { - "line": 1, - "column": 32 - }, - "end": { - "line": 1, - "column": 43 - } - } -] diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid-value/input.svelte b/packages/svelte/tests/validator/samples/window-binding-invalid-value/input.svelte deleted file mode 100644 index 7bfc3d58f..000000000 --- a/packages/svelte/tests/validator/samples/window-binding-invalid-value/input.svelte +++ /dev/null @@ -1 +0,0 @@ -<svelte:window bind:innerWidth={foo.bar.baz}/> \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid-width/_config.js b/packages/svelte/tests/validator/samples/window-binding-invalid-width/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/window-binding-invalid-width/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid-width/errors.json b/packages/svelte/tests/validator/samples/window-binding-invalid-width/errors.json deleted file mode 100644 index 031ba52d5..000000000 --- a/packages/svelte/tests/validator/samples/window-binding-invalid-width/errors.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "code": "invalid-binding", - "message": "'width' is not a valid binding on <svelte:window> (did you mean 'innerWidth'?)", - "start": { - "line": 1, - "column": 15 - }, - "end": { - "line": 1, - "column": 29 - } - } -] diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid-width/input.svelte b/packages/svelte/tests/validator/samples/window-binding-invalid-width/input.svelte deleted file mode 100644 index 88dcd5aa0..000000000 --- a/packages/svelte/tests/validator/samples/window-binding-invalid-width/input.svelte +++ /dev/null @@ -1 +0,0 @@ -<svelte:window bind:width={w}/> \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid/_config.js b/packages/svelte/tests/validator/samples/window-binding-invalid/_config.js deleted file mode 100644 index 64fdc120d..000000000 --- a/packages/svelte/tests/validator/samples/window-binding-invalid/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid/errors.json b/packages/svelte/tests/validator/samples/window-binding-invalid/errors.json index 64e6b090a..dc1fadfc0 100644 --- a/packages/svelte/tests/validator/samples/window-binding-invalid/errors.json +++ b/packages/svelte/tests/validator/samples/window-binding-invalid/errors.json @@ -1,13 +1,13 @@ [ { "code": "invalid-binding", - "message": "'potato' is not a valid binding on <svelte:window> — valid bindings are innerWidth, innerHeight, outerWidth, outerHeight, scrollX, scrollY, devicePixelRatio or online", + "message": "'potato' is not a valid binding", "start": { - "line": 1, + "line": 5, "column": 15 }, "end": { - "line": 1, + "line": 5, "column": 32 } } diff --git a/packages/svelte/tests/validator/samples/window-binding-invalid/input.svelte b/packages/svelte/tests/validator/samples/window-binding-invalid/input.svelte index 6ee989b7f..2b0f37578 100644 --- a/packages/svelte/tests/validator/samples/window-binding-invalid/input.svelte +++ b/packages/svelte/tests/validator/samples/window-binding-invalid/input.svelte @@ -1 +1,5 @@ -<svelte:window bind:potato={foo}/> \ No newline at end of file +<script> + let foo; +</script> + +<svelte:window bind:potato={foo}/> diff --git a/playgrounds/demo/package.json b/playgrounds/demo/package.json index 6704c8cf7..410bbab35 100644 --- a/playgrounds/demo/package.json +++ b/playgrounds/demo/package.json @@ -4,18 +4,17 @@ "version": "0.0.1", "type": "module", "scripts": { + "prepare": "node scripts/create-app-svelte.js", "dev": "vite --host", "ssr": "node ./server.js", "build": "vite build", "preview": "vite preview" }, - "dependencies": { - "svelte": "workspace:*" - }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^2.5.1", "express": "^4.18.2", "nodemon": "^3.0.1", + "svelte": "workspace:*", "vite": "^4.4.9" } } diff --git a/playgrounds/demo/scripts/App.template.svelte b/playgrounds/demo/scripts/App.template.svelte new file mode 100644 index 000000000..e51190c18 --- /dev/null +++ b/playgrounds/demo/scripts/App.template.svelte @@ -0,0 +1,8 @@ +<script lang="ts"> + function openInEditor() { + fetch('./__open-in-editor?file=src/App.svelte'); + } +</script> + +<h1>Demo App</h1> +<button class="open-in-editor" on:click={openInEditor}>edit App.svelte</button> diff --git a/playgrounds/demo/scripts/create-app-svelte.js b/playgrounds/demo/scripts/create-app-svelte.js new file mode 100644 index 000000000..478fa782a --- /dev/null +++ b/playgrounds/demo/scripts/create-app-svelte.js @@ -0,0 +1,6 @@ +import fs from 'node:fs'; +const destination = new URL('../src/App.svelte', import.meta.url); +if (!fs.existsSync(destination)) { + const template = new URL('./App.template.svelte', import.meta.url); + fs.writeFileSync(destination, fs.readFileSync(template, 'utf-8'), 'utf-8'); +} diff --git a/playgrounds/demo/vite.config.js b/playgrounds/demo/vite.config.js index b77353a79..de42e9cfd 100644 --- a/playgrounds/demo/vite.config.js +++ b/playgrounds/demo/vite.config.js @@ -2,5 +2,9 @@ import { defineConfig } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; export default defineConfig({ - plugins: [svelte()] + plugins: [svelte()], + optimizeDeps: { + // svelte is a local workspace package, optimizing it would require dev server restarts with --force for every change + exclude: ['svelte'] + } }); diff --git a/playgrounds/sandbox/run.js b/playgrounds/sandbox/run.js index e387bd44e..7cfe4eff6 100644 --- a/playgrounds/sandbox/run.js +++ b/playgrounds/sandbox/run.js @@ -35,6 +35,7 @@ for (const generate of ['client', 'server']) { const source = fs.readFileSync(input, 'utf-8'); const output_js = `${cwd}/output/${generate}/${file}.js`; + const output_map = `${cwd}/output/${generate}/${file}.js.map`; const output_css = `${cwd}/output/${generate}/${file}.css`; mkdirp(path.dirname(output_js)); @@ -48,12 +49,17 @@ for (const generate of ['client', 'server']) { } const compiled = compile(source, { + dev: true, filename: input, generate, runes: argv.runes }); - fs.writeFileSync(output_js, compiled.js.code); + fs.writeFileSync( + output_js, + compiled.js.code + '\n//# sourceMappingURL=' + path.basename(output_map) + ); + fs.writeFileSync(output_map, compiled.js.map.toString()); if (compiled.css) { fs.writeFileSync(output_css, compiled.css.code); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 408196925..7c316ce97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,7 @@ importers: version: 2.26.2 '@sveltejs/eslint-config': specifier: ^6.0.4 - version: 6.0.4(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(eslint-config-prettier@9.0.0)(eslint-plugin-svelte@2.35.0)(eslint-plugin-unicorn@49.0.0)(eslint@8.53.0)(typescript@5.2.2) + version: 6.0.4(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(eslint-config-prettier@9.0.0)(eslint-plugin-svelte@2.35.1)(eslint-plugin-unicorn@49.0.0)(eslint@8.53.0)(typescript@5.2.2) '@svitejs/changesets-changelog-github-compact': specifier: ^1.1.0 version: 1.1.0 @@ -77,8 +77,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 esrap: - specifier: ^1.1.1 - version: 1.1.1 + specifier: ^1.2.1 + version: 1.2.1 is-reference: specifier: ^3.0.1 version: 3.0.2 @@ -133,10 +133,6 @@ importers: version: 0.2.9 playgrounds/demo: - dependencies: - svelte: - specifier: workspace:* - version: link:../../packages/svelte devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^2.5.1 @@ -147,6 +143,9 @@ importers: nodemon: specifier: ^3.0.1 version: 3.0.1 + svelte: + specifier: workspace:* + version: link:../../packages/svelte vite: specifier: ^4.4.9 version: 4.5.0(@types/node@20.9.0)(lightningcss@1.22.1)(sass@1.69.5) @@ -257,8 +256,8 @@ importers: specifier: ^5.0.1 version: 5.0.2 esrap: - specifier: ^1.1.1 - version: 1.1.1 + specifier: ^1.2.1 + version: 1.2.1 publint: specifier: ^0.2.0 version: 0.2.5 @@ -357,11 +356,11 @@ importers: specifier: ^9.0.0 version: 9.1.6 prettier: - specifier: ^3.0.3 - version: 3.0.3 + specifier: ^3.1.0 + version: 3.1.0 prettier-plugin-svelte: - specifier: ^3.0.3 - version: 3.1.0(prettier@3.0.3)(svelte@4.2.3) + specifier: ^3.1.2 + version: 3.1.2(prettier@3.1.0)(svelte@4.2.3) sass: specifier: ^1.67.0 version: 1.69.5 @@ -2187,7 +2186,7 @@ packages: - supports-color dev: true - /@sveltejs/eslint-config@6.0.4(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(eslint-config-prettier@9.0.0)(eslint-plugin-svelte@2.35.0)(eslint-plugin-unicorn@49.0.0)(eslint@8.53.0)(typescript@5.2.2): + /@sveltejs/eslint-config@6.0.4(@typescript-eslint/eslint-plugin@5.62.0)(@typescript-eslint/parser@5.62.0)(eslint-config-prettier@9.0.0)(eslint-plugin-svelte@2.35.1)(eslint-plugin-unicorn@49.0.0)(eslint@8.53.0)(typescript@5.2.2): resolution: {integrity: sha512-U9pwmDs+DbmsnCgTfu6Bacdwqn0DuI1IQNSiQqTgzVyYfaaj+zy9ZoQCiJfxFBGXHkklyXuRHp0KMx346N0lcQ==} peerDependencies: '@typescript-eslint/eslint-plugin': '>= 5' @@ -2202,7 +2201,7 @@ packages: '@typescript-eslint/parser': 5.62.0(eslint@8.53.0)(typescript@5.2.2) eslint: 8.53.0 eslint-config-prettier: 9.0.0(eslint@8.53.0) - eslint-plugin-svelte: 2.35.0(eslint@8.53.0)(svelte@packages+svelte) + eslint-plugin-svelte: 2.35.1(eslint@8.53.0)(svelte@packages+svelte) eslint-plugin-unicorn: 49.0.0(eslint@8.53.0) typescript: 5.2.2 dev: true @@ -2230,7 +2229,7 @@ packages: svelte: 4.2.3 tiny-glob: 0.2.9 undici: 5.26.5 - vite: 4.5.0(@types/node@20.9.0)(lightningcss@1.22.1)(sass@1.69.5) + vite: 4.5.0(@types/node@18.18.9) transitivePeerDependencies: - supports-color @@ -2321,7 +2320,7 @@ packages: '@sveltejs/vite-plugin-svelte': 2.5.1(svelte@4.2.3)(vite@4.5.0) debug: 4.3.4 svelte: 4.2.3 - vite: 4.5.0(@types/node@20.9.0)(lightningcss@1.22.1)(sass@1.69.5) + vite: 4.5.0(@types/node@18.18.9) transitivePeerDependencies: - supports-color @@ -2355,7 +2354,7 @@ packages: magic-string: 0.30.5 svelte: 4.2.3 svelte-hmr: 0.15.3(svelte@4.2.3) - vite: 4.5.0(@types/node@20.9.0)(lightningcss@1.22.1)(sass@1.69.5) + vite: 4.5.0(@types/node@18.18.9) vitefu: 0.2.5(vite@4.5.0) transitivePeerDependencies: - supports-color @@ -2840,6 +2839,7 @@ packages: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 + dev: true /aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} @@ -2967,6 +2967,7 @@ packages: /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + dev: true /bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -3024,6 +3025,7 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 + dev: true /breakword@1.0.6: resolution: {integrity: sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==} @@ -3173,6 +3175,7 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 + dev: true /chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -3606,6 +3609,7 @@ packages: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} hasBin: true + dev: true /detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} @@ -3918,8 +3922,8 @@ packages: '@types/estree': 1.0.5 dev: true - /eslint-plugin-svelte@2.35.0(eslint@8.53.0)(svelte@packages+svelte): - resolution: {integrity: sha512-3WDFxNrkXaMlpqoNo3M1ZOQuoFLMO9+bdnN6oVVXaydXC7nzCJuGy9a0zqoNDHMSRPYt0Rqo6hIdHMEaI5sQnw==} + /eslint-plugin-svelte@2.35.1(eslint@8.53.0)(svelte@packages+svelte): + resolution: {integrity: sha512-IF8TpLnROSGy98Z3NrsKXWDSCbNY2ReHDcrYTuXZMbfX7VmESISR78TWgO9zdg4Dht1X8coub5jKwHzP0ExRug==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0-0 @@ -4063,8 +4067,8 @@ packages: estraverse: 5.3.0 dev: true - /esrap@1.1.1: - resolution: {integrity: sha512-PIgHGLP8VAG4Iao4CbOc+/5tgn+TpzGhyAuVCR5qgcFgPOUk9Ds61bH7hD2lbjDuu86lagofx3lVrRFWcIF+Gg==} + /esrap@1.2.1: + resolution: {integrity: sha512-dhkcOLfN/aDdMFI1iwPEcy/XqAZzGNfgfEJjZozy2tia6u0dQoZyXzkRshHTckuNsM+c0CYQndY+uRFe3N+AIQ==} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 '@types/estree': 1.0.5 @@ -4239,6 +4243,7 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: true /finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} @@ -4448,6 +4453,7 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -4693,6 +4699,7 @@ packages: /immutable@4.3.4: resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==} + dev: true /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -4773,6 +4780,7 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 + dev: true /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -4817,6 +4825,7 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + dev: true /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} @@ -4832,6 +4841,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true /is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} @@ -4852,6 +4862,7 @@ packages: /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + dev: true /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} @@ -5127,6 +5138,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /lightningcss-darwin-x64@1.22.1: @@ -5135,6 +5147,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /lightningcss-freebsd-x64@1.22.1: @@ -5143,6 +5156,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /lightningcss-linux-arm-gnueabihf@1.22.1: @@ -5151,6 +5165,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /lightningcss-linux-arm64-gnu@1.22.1: @@ -5159,6 +5174,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /lightningcss-linux-arm64-musl@1.22.1: @@ -5167,6 +5183,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /lightningcss-linux-x64-gnu@1.22.1: @@ -5175,6 +5192,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /lightningcss-linux-x64-musl@1.22.1: @@ -5183,6 +5201,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /lightningcss-win32-x64-msvc@1.22.1: @@ -5191,6 +5210,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /lightningcss@1.22.1: @@ -5208,6 +5228,7 @@ packages: lightningcss-linux-x64-gnu: 1.22.1 lightningcss-linux-x64-musl: 1.22.1 lightningcss-win32-x64-msvc: 1.22.1 + dev: true /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} @@ -5643,6 +5664,7 @@ packages: /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + dev: true /npm-bundled@2.0.1: resolution: {integrity: sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw==} @@ -5914,6 +5936,7 @@ packages: /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + dev: true /pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} @@ -6062,24 +6085,24 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier-plugin-svelte@3.1.0(prettier@3.0.3)(svelte@4.2.3): + /prettier-plugin-svelte@3.1.0(prettier@3.0.3)(svelte@packages+svelte): resolution: {integrity: sha512-96+AZxs2ESqIFA9j+o+DHqY+BsUglezfl553LQd6VOtTyJq5GPuBEb3ElxF2cerFzKlYKttlH/VcVmRNj5oc3A==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 dependencies: prettier: 3.0.3 - svelte: 4.2.3 + svelte: link:packages/svelte dev: true - /prettier-plugin-svelte@3.1.0(prettier@3.0.3)(svelte@packages+svelte): - resolution: {integrity: sha512-96+AZxs2ESqIFA9j+o+DHqY+BsUglezfl553LQd6VOtTyJq5GPuBEb3ElxF2cerFzKlYKttlH/VcVmRNj5oc3A==} + /prettier-plugin-svelte@3.1.2(prettier@3.1.0)(svelte@4.2.3): + resolution: {integrity: sha512-7xfMZtwgAWHMT0iZc8jN4o65zgbAQ3+O32V6W7pXrqNvKnHnkoyQCGCbKeUyXKZLbYE0YhFRnamfxfkEGxm8qA==} peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 dependencies: - prettier: 3.0.3 - svelte: link:packages/svelte + prettier: 3.1.0 + svelte: 4.2.3 dev: true /prettier@2.8.8: @@ -6094,6 +6117,12 @@ packages: hasBin: true dev: true + /prettier@3.1.0: + resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==} + engines: {node: '>=14'} + hasBin: true + dev: true + /pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -6259,6 +6288,7 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 + dev: true /rechoir@0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} @@ -6452,6 +6482,7 @@ packages: chokidar: 3.5.3 immutable: 4.3.4 source-map-js: 1.0.2 + dev: true /satori-html@0.3.2: resolution: {integrity: sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==} @@ -7230,6 +7261,7 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + dev: true /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} @@ -7618,6 +7650,7 @@ packages: sass: 1.69.5 optionalDependencies: fsevents: 2.3.3 + dev: true /vitefu@0.2.5(vite@4.5.0): resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} diff --git a/sites/svelte-5-preview/package.json b/sites/svelte-5-preview/package.json index 0a50a5776..38d281b10 100644 --- a/sites/svelte-5-preview/package.json +++ b/sites/svelte-5-preview/package.json @@ -18,7 +18,7 @@ "@sveltejs/kit": "^1.22.5", "@sveltejs/site-kit": "6.0.0-next.51", "@types/marked": "^5.0.1", - "esrap": "^1.1.1", + "esrap": "^1.2.1", "marked": "^9.0.0", "publint": "^0.2.0", "shiki": "^0.14.4", diff --git a/sites/svelte-5-preview/src/lib/CodeMirror.svelte b/sites/svelte-5-preview/src/lib/CodeMirror.svelte index 6ec16356e..ebeeef29d 100644 --- a/sites/svelte-5-preview/src/lib/CodeMirror.svelte +++ b/sites/svelte-5-preview/src/lib/CodeMirror.svelte @@ -205,15 +205,17 @@ return { from: word.from - 1, options: [ - { label: '$state', type: 'keyword', boost: 5 }, - { label: '$props', type: 'keyword', boost: 4 }, - { label: '$derived', type: 'keyword', boost: 3 }, - snip('$effect(() => {\n\t${}\n});', { label: '$effect', type: 'keyword', boost: 2 }), + { label: '$state', type: 'keyword', boost: 10 }, + { label: '$props', type: 'keyword', boost: 9 }, + { label: '$derived', type: 'keyword', boost: 8 }, + snip('$effect(() => {\n\t${}\n});', { label: '$effect', type: 'keyword', boost: 7 }), snip('$effect.pre(() => {\n\t${}\n});', { label: '$effect.pre', type: 'keyword', - boost: 1 - }) + boost: 6 + }), + { label: '$effect.active', type: 'keyword', boost: 5 }, + { label: '$inspect', type: 'keyword', boost: 4 } ] }; } diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/index.js b/sites/svelte-5-preview/src/lib/workers/bundler/index.js index 15c43529c..924f5becf 100644 --- a/sites/svelte-5-preview/src/lib/workers/bundler/index.js +++ b/sites/svelte-5-preview/src/lib/workers/bundler/index.js @@ -182,7 +182,7 @@ async function resolve_from_pkg(pkg, subpath, uid, pkg_url_base) { const [resolved] = resolve.exports(pkg, subpath, { browser: true, - conditions: ['svelte', 'production'] + conditions: ['svelte', 'development'] }) ?? []; return resolved; diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md index 19cbde822..192138540 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md @@ -40,6 +40,26 @@ class Todo { > この例では、コンパイラは `done` と `text` を、private field を参照する class prototype の `get`/`set` メソッドに変換します +オブジェクトと配列は [リアクティブになります](/#H4sIAAAAAAAAE42QwWrDMBBEf2URhUhUNEl7c21DviPOwZY3jVpZEtIqUBz9e-UUt9BTj7M784bdmZ21wciq48xsPyGr2MF7Jhl9-kXEKxrCoqNLQS2TOqqgPbWd7cgggU3TgCFCAw-RekJ-3Et4lvByEq-drbe_dlsPichZcFYZrT6amQto2pXw5FO88FUYtG90gUfYi3zvWrYL75vxL57zfA07_zfr23k1vjtt-aZ0bQTcbrDL5ZifZcAxKeS8lzDc8X0xDhJ2ItdbX1jlOZMb9VnjyCoKCfMpfwG975NFVwEAAA==): + +```svelte +<script> + let numbers = $state([1, 2, 3]); +</script> + +<button onclick={() => numbers.push(numbers.length + 1)}> + push +</button> + +<button onclick={() => numbers.pop()}> pop </button> + +<p> + {numbers.join(' + ') || 0} + = + {numbers.reduce((a, b) => a + b, 0)} +</p> +``` + ### What this replaces 非 rune モードでは、`let` 宣言がありそれがどこかで更新されている場合にリアクティブなステートとして扱われます。`$state(...)` はアプリのどこでも動作しますが、`let` の場合はコンポーネントのトップレベルにある場合にのみリアクティブなステートとして振る舞います。 @@ -51,11 +71,11 @@ class Todo { ```diff <script> let count = $state(0); -+ let double = $derived(count * 2); ++ let doubled = $derived(count * 2); </script> <button on:click={() => count++}> - {double} + {doubled} </button> +<p>{count} doubled is {doubled}</p> @@ -92,13 +112,13 @@ class Todo { ```diff <script> let count = $state(0); - let double = $derived(count * 2); + let doubled = $derived(count * 2); + $effect(() => { + // runs when the component is mounted, and again -+ // whenever `count` or `double` change, ++ // whenever `count` or `doubled` change, + // after the DOM has been updated -+ console.log({ count, double }); ++ console.log({ count, doubled }); + + return () => { + // if a callback is provided, it will run @@ -110,7 +130,7 @@ class Todo { </script> <button on:click={() => count++}> - {double} + {doubled} </button> <p>{count} doubled is {doubled}</p> @@ -158,7 +178,7 @@ class Todo { </script> <div bind:this={div}> - {#each message as message} + {#each messages as message} <p>{message}</p> {/each} </div> @@ -168,6 +188,45 @@ class Todo { これまで `beforeUpdate` を使用してきた場所で使うことができます。`afterUpdate` と同様に、Svelte 5 では `beforeUpdate` は非推奨となります。 +## `$effect.active` + +`$effect.active` rune は、コードが effect の内側で実行されているのか、テンプレート内で実行されているのかを教えてくれる高度な機能です ([デモ](/#H4sIAAAAAAAAE3XP0QrCMAwF0F-JRXAD595rLfgdzodRUyl0bVgzQcb-3VYFQfExl5tDMgvrPCYhT7MI_YBCiiOR2Aq-UxnSDT1jnlOcRlMSlczoiHUXOjYxpOhx5-O12rgAJg4UAwaGhDyR3Gxhjdai4V1v2N2wqus9tC3Y3ifMQjbehaqq4aBhLtEv_Or893icCsdLve-Caj8nBkU67zMO5HtGCfM3sKiWNKhV0zwVaBqd3x3ixVmHFyFLuJyXB-moOe8pAQAA)): + +```svelte +<script> + console.log('in component setup:', $effect.active()); // false + + $effect(() => { + console.log('in effect:', $effect.active()); // true + }); +</script> + +<p>in template: {$effect.active()}</p> <!-- true --> +``` + +(例えば) これを子の effect に置くことによって、サブスクリプションなどをメモリリークなしで追加することができます。 + +## `$effect.root` + +`$effect.root` rune は、自動クリーンアップされない非追跡のスコープを作成することができる高度な機能です。 +これは手動でコントロールしたいネストした effect を扱うのに有用です。また、この rune によって、コンポーネントの初期化フェーズ以外で effect を作成することができます。 + +```svelte +<script> + let count = $state(0); + + const cleanup = $effect.root(() => { + $effect(() => { + console.log(count); + }); + + return () => { + console.log('effect root cleanup'); + }; + }); +</script> +``` + ## `$props` コンポーネントの props を宣言するには、`$props` rune を使用します: @@ -196,12 +255,58 @@ type MyProps = any; let { a, b, c, ...everythingElse } = $props<MyProps>(); ``` +親コンポーネントが `bind:` を使用していない限り、props を変更することはできません。開発時には、props を変更使用とするとエラーとなります。 + ### What this replaces `$props` は、props を宣言する `export let` と `export { x as y }` 構文を置き換えます。また、`$$props` と `$$restProps`、そして知る人ぞ知る `interface $$Props {...}` construct も置き換えます。 なお、`export const` と `export function` を使用してコンポーネントのユーザーに諸々を公開することは可能です (例えば、`bind:this` を使用する場合など)。 +## `$inspect` + +`$inspect` rune は大まかには `console.log` と一緒ですが、与えられた引数が変わるたびに再実行されるという点が異なります。 +`$inspect` はリアクティブな state を深く(deeply)追跡します。つまり、[fine-grained reactivity](/docs/fine-grained-reactivity) により、 +オブジェクトや配列の内側で何かしらが更新されると、再実行されます。([デモ:](/#H4sIAAAAAAAAE0WQ0W6DMAxFf8WKKhXUquyZAtIe9w1lEjS4ENU4EXFaTRH_Plq69fH6nutrOaqLIfQqP0XF7YgqV5_Oqb2SH_cQ_oYkuGhvw6Qfk8LryTipaq6FUEDbwAIlbLy0gslHevxzRvS-7fHtbQckstsnsTAbw96hliSuS_b_iTk9QpbB3RAtFntLeCDbw31AhuYJN2AnaF6BBvTQco81F9n7PC7OQcQyWNZk9LWMSQpltZbtdnP1xXrCEVmKbCWXVGHYBYGz4S6_tRSwjK-SGbJqecRoO3Mx2KlcpoDz9_wLBx9LikMBAAA=)) + +```svelte +<script> + let count = $state(0); + let message = $state('hello'); + + $inspect({ count, message }); // will console.log when `count` or `message` change +</script> + +<button onclick={() => count++}>Increment</button> +<input bind:value={message} /> +``` + +また、コールバックが提供される場合は、`console.log` の代わりにそのコールバックが実行されます。コールバックの第一引数は現在の値です。 +第二引数は `"init"` か `"update"` です。[デモ:](/#H4sIAAAAAAAAE0VP24qDMBD9lSEUqlTqPlsj7ON-w1qojWM3rE5CMmkpkn_fxFL26XBuw5lVTHpGL5rvVdCwoGjEp7WiEvy0mfg7zoyJexOcykrrldOWu556npFBmUAMEnaeB8biozwlJ3k7Td6i4mILVPDGfLgE2cGaUz3rCYqsgZQS9sGO6cq-fLs9j3gNtxu6E9Q1GAcXZcibGY_sBoWXKmuPn1S6o4OnCfAYiF_lmCHmQW39v5raa2A2BIbUrNWvXIttz7bvcIjdFymHCxK39SvZpf8XM-pJ4ygadgHjOf4B8TXIiDoBAAA=) + +```svelte +<script> + let count = $state(0); + + $inspect(count, (count, type) => { + if (type === 'update') { + debugger; // or `console.trace`, or whatever you want + } + }); +</script> + +<button onclick={() => count++}>Increment</button> +``` + +第二引数に `console.trace` を渡すと、ある変更がどこで行われたかを簡単に確認することができます: + +```js +// @errors: 2304 +$inspect(stuff, console.trace); +``` + +> `$inspect` は開発時にのみ動作します。 + ## How to opt in 現在の Svelte のコードは調整することなく引き続き動作します。Svelte 4 の構文を使用しているコンポーネントは Rune を使用するコンポーネントを使用することができます。その逆も同様に使用できます。 diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md index a1114007d..cb6dd9b11 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md @@ -2,7 +2,7 @@ title: Snippets --- -Snippets, and _render tags_, are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like [this](/#H4sIAAAAAAAAE5VUYW-kIBD9K8Tmsm2yXXRzvQ-s3eR-R-0HqqOQKhAZb9sz_vdDkV1t000vRmHewMx7w2AflbIGG7GnPlK8gYhFv42JthG-m9Gwf6BGcLbVXZuPSGrzVho8ZirDGpDIhldgySN5GpEMez9kaNuckY1ANJZRamRuu2ZnhEZt6a84pvs43mzD4pMsUDDi8DMkQFYCGdkvsJwblFq5uCik9bmJ4JZwUkv1eoknWigX2eGNN6aGXa6bjV8ybP-X7sM36T58SVcrIIV2xVIaA41xeD5kKqWXuqpUJEefOqVuOkL9DfBchGrzWfu0vb-RpTd3o-zBR045Ga3HfuE5BmJpKauuhbPtENlUF2sqR9jqpsPSxWsMrlngyj3VJiyYjJXb1-lMa7IWC-iSk2M5Zzh-SJjShe-siq5kpZRPs55BbSGU5YPyte4vVV_VfFXxVb10dSLf17pS2lM5HnpPxw4Zpv6x-F57p0jI3OKlVnhv5V9wPQrNYQQ9D_f6aGHlC89fq1Z3qmDkJCTCweOGF4VUFSPJvD_DhreVdA0eu8ehJJ5x91dBaBkpWm3ureCFPt3uzRv56d4kdp-2euG38XZ6dsnd3ZmPG9yRBCrzRUvi-MccOdwz3qE-fOZ7AwAhlrtTUx3c76vRhSwlFBHDtoPhefgHX3dM0PkEAAA=)... +Snippet、そして _render タグ_ は、コンポーネントの内側で再利用できるマークアップのチャンクを作成することができます。[このような](/#H4sIAAAAAAAAE5VUYW-kIBD9K8Tmsm2yXXRzvQ-s3eR-R-0HqqOQKhAZb9sz_vdDkV1t000vRmHewMx7w2AflbIGG7GnPlK8gYhFv42JthG-m9Gwf6BGcLbVXZuPSGrzVho8ZirDGpDIhldgySN5GpEMez9kaNuckY1ANJZRamRuu2ZnhEZt6a84pvs43mzD4pMsUDDi8DMkQFYCGdkvsJwblFq5uCik9bmJ4JZwUkv1eoknWigX2eGNN6aGXa6bjV8ybP-X7sM36T58SVcrIIV2xVIaA41xeD5kKqWXuqpUJEefOqVuOkL9DfBchGrzWfu0vb-RpTd3o-zBR045Ga3HfuE5BmJpKauuhbPtENlUF2sqR9jqpsPSxWsMrlngyj3VJiyYjJXb1-lMa7IWC-iSk2M5Zzh-SJjShe-siq5kpZRPs55BbSGU5YPyte4vVV_VfFXxVb10dSLf17pS2lM5HnpPxw4Zpv6x-F57p0jI3OKlVnhv5V9wPQrNYQQ9D_f6aGHlC89fq1Z3qmDkJCTCweOGF4VUFSPJvD_DhreVdA0eu8ehJJ5x91dBaBkpWm3ureCFPt3uzRv56d4kdp-2euG38XZ6dsnd3ZmPG9yRBCrzRUvi-MccOdwz3qE-fOZ7AwAhlrtTUx3c76vRhSwlFBHDtoPhefgHX3dM0PkEAAA=)重複するコードを書く代わりに… ```svelte {#each images as image} @@ -32,7 +32,7 @@ Snippets, and _render tags_, are a way to create reusable chunks of markup insid {/each} ``` -...you can write [this](/#H4sIAAAAAAAAE5VUYW-bMBD9KxbRlERKY4jWfSA02n5H6QcXDmwVbMs-lnaI_z6D7TTt1moTAnPvzvfenQ_GpBEd2CS_HxPJekjy5IfWyS7BFz0b9id0CM62ajDVjBS2MkLjqZQldoBE9KwFS-7I_YyUOPqlRGuqnKw5orY5pVpUduj3mitUln5LU3pI0_UuBp9FjTwnDr9AHETLMSeHK6xiGoWSLi9yYT034cwSRjohn17zcQPNFTs8s153sK9Uv_Yh0-5_5d7-o9zbD-UqCaRWrllSYZQxLw_HUhb0ta-y4NnJUxfUvc7QuLJSaO0a3oh2MLBZat8u-wsPnXzKQvTtVVF34xK5d69ThFmHEQ4SpzeVRediTG8rjD5vBSeN3E5JyHh6R1DQK9-iml5kjzQUN_lSgVU8DhYLx7wwjSvRkMDvTjiwF4zM1kXZ7DlF1eN3A7IG85e-zRrYEjjm0FkI4Cc7Ripm0pHOChexhcWXzreeZyRMU6Mk3ljxC9w4QH-cQZ_b3T5pjHxk1VNr1CDrnJy5QDh6XLO6FrLNSRb2l9gz0wo3S6m7HErSgLsPGMHkpDZK31jOanXeHPQz-eruLHUP0z6yTbpbrn223V70uMXNSpQSZjpL0y8hcxxpNqA6_ql3BQAxlxvfpQ_uT9GrWjQC6iRHM8D0MP0GQsIi92QEAAA=): +…[このように](/#H4sIAAAAAAAAE5VUYW-bMBD9KxbRlERKY4jWfSA02n5H6QcXDmwVbMs-lnaI_z6D7TTt1moTAnPvzvfenQ_GpBEd2CS_HxPJekjy5IfWyS7BFz0b9id0CM62ajDVjBS2MkLjqZQldoBE9KwFS-7I_YyUOPqlRGuqnKw5orY5pVpUduj3mitUln5LU3pI0_UuBp9FjTwnDr9AHETLMSeHK6xiGoWSLi9yYT034cwSRjohn17zcQPNFTs8s153sK9Uv_Yh0-5_5d7-o9zbD-UqCaRWrllSYZQxLw_HUhb0ta-y4NnJUxfUvc7QuLJSaO0a3oh2MLBZat8u-wsPnXzKQvTtVVF34xK5d69ThFmHEQ4SpzeVRediTG8rjD5vBSeN3E5JyHh6R1DQK9-iml5kjzQUN_lSgVU8DhYLx7wwjSvRkMDvTjiwF4zM1kXZ7DlF1eN3A7IG85e-zRrYEjjm0FkI4Cc7Ripm0pHOChexhcWXzreeZyRMU6Mk3ljxC9w4QH-cQZ_b3T5pjHxk1VNr1CDrnJy5QDh6XLO6FrLNSRb2l9gz0wo3S6m7HErSgLsPGMHkpDZK31jOanXeHPQz-eruLHUP0z6yTbpbrn223V70uMXNSpQSZjpL0y8hcxxpNqA6_ql3BQAxlxvfpQ_uT9GrWjQC6iRHM8D0MP0GQsIi92QEAAA=)書くことができます: ```diff +{#snippet figure(image)} @@ -58,7 +58,7 @@ Snippets, and _render tags_, are a way to create reusable chunks of markup insid {/each} ``` -A snippet can have at most one parameter. You can destructure it, just like a function argument ([demo](/#H4sIAAAAAAAAE5VTYW-bMBD9KyeiKYlEY4jWfSAk2n5H6QcXDmwVbMs2SzuL_z6DTRqp2rQJ2Ycfd_ced2eXtLxHkxRPLhF0wKRIfiiVpIl9V_PB_MTeoj8bOep6RkpTa67spRKV7dECH2iHBs7wNCOVdcFU1ui6gC2zVpmCEMVrMw4HxaSVhnzLMnLMsm26Ol95Y1kBHr9BDHnHbAHHO6ymynIpfF7LuAncwKgBCj0Xrx_5mMb2jh3f6KB6PNRy2AaXKf1fuY__KPfxj3KlQGikL5aQdpUxm-dTJUryUVdRsvwSqEviX2fIbYzgSvmCt7wbNe4ceMUpRIoUFkkpBBkw7ZfMZXC-BLKSDx3Q3p5djJrA-SR-X4K9DdHT6u-jo-flFlKSO3ThIDcSR6LIKUhGWrN1QGhs16LLbXgbjoe5U1PkozCfzu7uy2WtpfuuUTSo1_9ffPZrJKGLoyuwNxjBv0Q4wmdSR2aFi9jS2Pc-FIrlEKeilcI-GP4LfVtxOM1gyO1XSLp6vtD6tdNyFE0BV8YtngKuaNNw0RWQx_jKDlR33M9E5h-PQhZxfxEt6gIaLdWDYbSR191RvcFXv_LMb7p7obssXZ5Dvt_f9HgzdzZKibOZZ9mXmHkdTTpaefqsd4OIay4_hksd_I0fZMNbjk1SWD3i9Dz9BpdEPu8sBAAA)): +snippet では最大で1つだけパラメータを受け取ることができます。関数の引数のように、分割することができます ([デモ](/#H4sIAAAAAAAAE5VTYW-bMBD9KyeiKYlEY4jWfSAk2n5H6QcXDmwVbMs2SzuL_z6DTRqp2rQJ2Ycfd_ced2eXtLxHkxRPLhF0wKRIfiiVpIl9V_PB_MTeoj8bOep6RkpTa67spRKV7dECH2iHBs7wNCOVdcFU1ui6gC2zVpmCEMVrMw4HxaSVhnzLMnLMsm26Ol95Y1kBHr9BDHnHbAHHO6ymynIpfF7LuAncwKgBCj0Xrx_5mMb2jh3f6KB6PNRy2AaXKf1fuY__KPfxj3KlQGikL5aQdpUxm-dTJUryUVdRsvwSqEviX2fIbYzgSvmCt7wbNe4ceMUpRIoUFkkpBBkw7ZfMZXC-BLKSDx3Q3p5djJrA-SR-X4K9DdHT6u-jo-flFlKSO3ThIDcSR6LIKUhGWrN1QGhs16LLbXgbjoe5U1PkozCfzu7uy2WtpfuuUTSo1_9ffPZrJKGLoyuwNxjBv0Q4wmdSR2aFi9jS2Pc-FIrlEKeilcI-GP4LfVtxOM1gyO1XSLp6vtD6tdNyFE0BV8YtngKuaNNw0RWQx_jKDlR33M9E5h-PQhZxfxEt6gIaLdWDYbSR191RvcFXv_LMb7p7obssXZ5Dvt_f9HgzdzZKibOZZ9mXmHkdTTpaefqsd4OIay4_hksd_I0fZMNbjk1SWD3i9Dz9BpdEPu8sBAAA)): ```svelte {#snippet figure({ src, caption, width, height })} @@ -71,7 +71,7 @@ A snippet can have at most one parameter. You can destructure it, just like a fu ## Snippet scope -Snippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the `<script>` tag or in `{#each ...}` blocks ([demo](/#H4sIAAAAAAAAE12P0QrCMAxFfyWrwhSEvc8p-h1OcG5RC10bmkyQ0n-3HQPBx3vCPUmCemiDrOpLULYbUdXqTKR2Sj6UA7_RCKbMbvJ9Jg33XpMcW9uKQYEAIzJ3T4QD3LSUDE-PnYA4YET4uOkGMc3W5B3xZrtvbVP9HDas2GqiZHqhMW6Tr9jGbG_oOCMImcUCwrIpFk1FqRyqpRpn0cmjHdAvnrIzuscyq_4nd3dPPD01ukE_NA6qFj9hvMYvGjJADw8BAAA=))... +snippet はコンポーネントの内側ならどこでも宣言することができます。snippet は、その外側で宣言された値、例えば、`<script>` タグや `{#each ...}` ブロックで宣言された値を参照することができます ([デモ](/#H4sIAAAAAAAAE12P0QrCMAxFfyWrwhSEvc8p-h1OcG5RC10bmkyQ0n-3HQPBx3vCPUmCemiDrOpLULYbUdXqTKR2Sj6UA7_RCKbMbvJ9Jg33XpMcW9uKQYEAIzJ3T4QD3LSUDE-PnYA4YET4uOkGMc3W5B3xZrtvbVP9HDas2GqiZHqhMW6Tr9jGbG_oOCMImcUCwrIpFk1FqRyqpRpn0cmjHdAvnrIzuscyq_4nd3dPPD01ukE_NA6qFj9hvMYvGjJADw8BAAA=))… ```svelte <script> @@ -86,26 +86,26 @@ Snippets can be declared anywhere inside your component. They can reference valu {@render hello('bob')} ``` -...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings): +…そして snippet は同じレキシカルスコープ (lexical scope) にある全てのもの (つまり、兄弟とその兄弟の子(siblings and children of those siblings)) から '見える(visible)' ようになっています: ```svelte <div> {#snippet x()} {#snippet y()}...{/snippet} - <!-- this is fine --> + <!-- これは動作します --> {@render y()} {/snippet} - <!-- this will error, as `y` is not in scope --> + <!-- これはエラーになります。`y` はスコープ外だからです --> {@render y()} </div> -<!-- this will also error, as `x` is not in scope --> +<!-- これもまたエラーになります。`x` はスコープ外だからです --> {@render x()} ``` -Snippets can reference themselves and each other ([demo](/#H4sIAAAAAAAAE2WPTQqDMBCFrxLiRqH1Zysi7TlqF1YnENBJSGJLCYGeo5tesUeosfYH3c2bee_jjaWMd6BpfrAU6x5oTvdS0g01V-mFPkNnYNRaDKrxGxto5FKCIaeu1kYwFkauwsoUWtZYPh_3W5FMY4U2mb3egL9kIwY0rbhgiO-sDTgjSEqSTvIDs-jiOP7i_MHuFGAL6p9BtiSbOTl0GtzCuihqE87cqtyam6WRGz_vRcsZh5bmRg3gju4Fptq_kzQBAAA=)): +snippet は自分自身を参照することができますし、別の snippet を参照することもできます ([demo](/#H4sIAAAAAAAAE2WPTQqDMBCFrxLiRqH1Zysi7TlqF1YnENBJSGJLCYGeo5tesUeosfYH3c2bee_jjaWMd6BpfrAU6x5oTvdS0g01V-mFPkNnYNRaDKrxGxto5FKCIaeu1kYwFkauwsoUWtZYPh_3W5FMY4U2mb3egL9kIwY0rbhgiO-sDTgjSEqSTvIDs-jiOP7i_MHuFGAL6p9BtiSbOTl0GtzCuihqE87cqtyam6WRGz_vRcsZh5bmRg3gju4Fptq_kzQBAAA=)): ```svelte {#snippet blastoff()} @@ -126,7 +126,7 @@ Snippets can reference themselves and each other ([demo](/#H4sIAAAAAAAAE2WPTQqDM ## Passing snippets to components -Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/#H4sIAAAAAAAAE41SwY6bMBD9lRGplKQlYRMpF5ZF7T_0ttmDwSZYJbZrT9pGlv-9g4Fkk-xhxYV5vHlvhjc-aWQnXJK_-kSxo0jy5IcxSZrg2fSF-yM6FFQ7fbJ1jxSuttJguVd7lEejLcJPVnUCGquPMF9nsVoPjfNnohGx1sohMU4SHbzAa4_t0UNvmcOcGUNDzFP4jeccdikYK2v6sIWQ3lErpui5cDdPF_LmkVy3wlp5Vd5e2U_rHYSe_kYjFtl1KeVnTkljBEIrGBd2sYy8AtsyLlBk9DYhJHtTR_UbBDWybkR8NkqHWyOr_y74ZMNLz9f9AoG6ePkOJLMHLBp-xISvcPf11r0YUuMM2Ysfkgngh5XphUYKkJWU_FFz2UjBkxztSYT0cihR4LOn0tGaPrql439N-7Uh0Dl8MVYbt1jeJ1Fg7xDb_Uw2Y18YQqZ_S2U5FH1pS__dCkWMa3C0uR0pfQRTg89kE4bLLLDS_Dxy_Eywuo1TAnPAw4fqY1rvtH3W9w35ZZMgvU3jq8LhedwkguCHRhT_cMU6eVA5dKLB5wGutCWjlTOslupAxxrxceKoD2hzhe2qbmXHF1v1bbOcNCtW_zpYfVI8h5kQ4qY3mueHTlesW2C7TOEO4hcdwzgf3Nc7cZxUKKC4yuNhvIX_MlV_Xk0EAAA=)): +テンプレートの中では、snippet は他と同じように値として扱うことができます。そのため、props としてコンポーネントにわたすことができます ([デモ](/#H4sIAAAAAAAAE41SwY6bMBD9lRGplKQlYRMpF5ZF7T_0ttmDwSZYJbZrT9pGlv-9g4Fkk-xhxYV5vHlvhjc-aWQnXJK_-kSxo0jy5IcxSZrg2fSF-yM6FFQ7fbJ1jxSuttJguVd7lEejLcJPVnUCGquPMF9nsVoPjfNnohGx1sohMU4SHbzAa4_t0UNvmcOcGUNDzFP4jeccdikYK2v6sIWQ3lErpui5cDdPF_LmkVy3wlp5Vd5e2U_rHYSe_kYjFtl1KeVnTkljBEIrGBd2sYy8AtsyLlBk9DYhJHtTR_UbBDWybkR8NkqHWyOr_y74ZMNLz9f9AoG6ePkOJLMHLBp-xISvcPf11r0YUuMM2Ysfkgngh5XphUYKkJWU_FFz2UjBkxztSYT0cihR4LOn0tGaPrql439N-7Uh0Dl8MVYbt1jeJ1Fg7xDb_Uw2Y18YQqZ_S2U5FH1pS__dCkWMa3C0uR0pfQRTg89kE4bLLLDS_Dxy_Eywuo1TAnPAw4fqY1rvtH3W9w35ZZMgvU3jq8LhedwkguCHRhT_cMU6eVA5dKLB5wGutCWjlTOslupAxxrxceKoD2hzhe2qbmXHF1v1bbOcNCtW_zpYfVI8h5kQ4qY3mueHTlesW2C7TOEO4hcdwzgf3Nc7cZxUKKC4yuNhvIX_MlV_Xk0EAAA=)): ```svelte <script> @@ -156,10 +156,10 @@ Within the template, snippets are values just like any other. As such, they can <Table data={fruits} {header} {row} /> ``` -As an authoring convenience, snippets declare directly _inside_ a component implicitly become props _on_ the component ([demo](/#H4sIAAAAAAAAE41Sy27bMBD8lYVcwHYrW4kBXxRFaP-htzgHSqQsojLJkuu2BqF_74qUrfhxCHQRh7MzO9z1SSM74ZL8zSeKHUSSJz-MSdIET2Y4uD-iQ0Fnp4-2HpDC1VYaLHdqh_JgtEX4yapOQGP1AebrLJzWsXD-QjQi1lo5JMZRooNXeBuwHXoYLHOYM2OoiXkKv_GUwzYFY2VNFxvo0xtqxRR9F-7z04X8fE-uW2GtnJQ3E_tpvYV-oL9Ti0U2hVJFjMMZslcfW-5DWj9zShojEFrBuLCLZR_9CmzLQCwy-psw8rxBgvkNhhpZd8F8NppE7Stbq_8u-GTKS8_XQ9Keqnl5BZP1AzTYP2bDV7i7_9hLEeda0iocNJeNFDzJ0R5Fn142JzA-uzsdBfLhldPxPdMhIPS0H1-M1cYtlnejwdBDfBXZjHXTFOg4BhuOtvTfrVDEmAZG2ew5ezYV-Ew2fVzVAivNTyPHzwSr29AlMAe8f6g-zuWDts-GusAmdBSkv3P7qnB4GpMEEHwsRPEPV6yTe5VDJxp8iXClLRmtnGG1VHva3oCPHQd9QJsrbFd1Kzu-2Khvz8uzZsXqX3urj4rnMBNCXNUG83zf6Yp1C2yXKdxA_KJjGOfRfb0Vh7MKDShEuV-M9_4_nq6svF4EAAA=)): +オーサリングの都合上、snippet が直接コンポーネントの _内側で_ 宣言された場合、暗黙的にそのコンポーネントの props になります ([デモ](/#H4sIAAAAAAAAE41Sy27bMBD8lYVcwHYrW4kBXxRFaP-htzgHSqQsojLJkuu2BqF_74qUrfhxCHQRh7MzO9z1SSM74ZL8zSeKHUSSJz-MSdIET2Y4uD-iQ0Fnp4-2HpDC1VYaLHdqh_JgtEX4yapOQGP1AebrLJzWsXD-QjQi1lo5JMZRooNXeBuwHXoYLHOYM2OoiXkKv_GUwzYFY2VNFxvo0xtqxRR9F-7z04X8fE-uW2GtnJQ3E_tpvYV-oL9Ti0U2hVJFjMMZslcfW-5DWj9zShojEFrBuLCLZR_9CmzLQCwy-psw8rxBgvkNhhpZd8F8NppE7Stbq_8u-GTKS8_XQ9Keqnl5BZP1AzTYP2bDV7i7_9hLEeda0iocNJeNFDzJ0R5Fn142JzA-uzsdBfLhldPxPdMhIPS0H1-M1cYtlnejwdBDfBXZjHXTFOg4BhuOtvTfrVDEmAZG2ew5ezYV-Ew2fVzVAivNTyPHzwSr29AlMAe8f6g-zuWDts-GusAmdBSkv3P7qnB4GpMEEHwsRPEPV6yTe5VDJxp8iXClLRmtnGG1VHva3oCPHQd9QJsrbFd1Kzu-2Khvz8uzZsXqX3urj4rnMBNCXNUG83zf6Yp1C2yXKdxA_KJjGOfRfb0Vh7MKDShEuV-M9_4_nq6svF4EAAA=)): ```svelte -<!-- this is semantically the same as the above --> +<!-- セマンティクス上、これは上記の例と同じです --> <Table data={fruits}> {#snippet header()} <th>fruit</th> @@ -177,7 +177,7 @@ As an authoring convenience, snippets declare directly _inside_ a component impl </Table> ``` -Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet ([demo](/#H4sIAAAAAAAAE41S247aMBD9lVFYCegGsiDxks1G7T_0bdkHJ3aI1cR27aEtsvzvtZ0LZeGhiiJ5js-cmTMemzS8YybJ320iSM-SPPmmVJImeFEhML9Yh8zHRp51HZDC1JorLI_iiLxXUiN8J1XHoNGyh-U2i9F2SFy-epon1lIY9IwzRwNv8B6wI1oIJXNYEqV8E8sUfuIlh0MKSvPaX-zBpZ-oFRH-m7m7l5m8uyfXLdOaX5X3V_bL9gAu0D98i0V2NSWKwQ4lSN7s0LKLbgtsyxgXmT9NiBe-iaP-DYISSTcj4bcLI7hSDEHL3yu6dkPfBdLS0m1o3nk-LW9gX-gBGss9ZsMXuLu32VjZBdfRaelft5eUN5zRJEd9Zi6dlyEy_ncdOm_IxsGlULe8o5qJNFgE5x_9SWmpzGp9N2-MXQxz4c2cOQ-lZWQyF0Jd2q_-mjI9U1fr4FBPE8iuKTbjjRt2sMBK0svIsQtG6jb2CsQAdQ_1x9f5R9tmIS-yPToK-tNkQRQGL6ObCIIdEpH9wQ3p-Enk0LEGXwe4ktoX2hhFai5Ofi0jPnYc9QF1LrDdRK-rvXjerSfNitQ_TlqeBc1hwRi7yY3F81MnK9KtsF2n8Amis44ilA7VtwfWTyr-kaKV-_X4cH8BTOhfRzcEAAA=)): +コンポーネントタグの内側にある、snippet 宣言ではないコンテンツは、暗黙的に `children` snippet の一部になります ([デモ](/#H4sIAAAAAAAAE41S247aMBD9lVFYCegGsiDxks1G7T_0bdkHJ3aI1cR27aEtsvzvtZ0LZeGhiiJ5js-cmTMemzS8YybJ320iSM-SPPmmVJImeFEhML9Yh8zHRp51HZDC1JorLI_iiLxXUiN8J1XHoNGyh-U2i9F2SFy-epon1lIY9IwzRwNv8B6wI1oIJXNYEqV8E8sUfuIlh0MKSvPaX-zBpZ-oFRH-m7m7l5m8uyfXLdOaX5X3V_bL9gAu0D98i0V2NSWKwQ4lSN7s0LKLbgtsyxgXmT9NiBe-iaP-DYISSTcj4bcLI7hSDEHL3yu6dkPfBdLS0m1o3nk-LW9gX-gBGss9ZsMXuLu32VjZBdfRaelft5eUN5zRJEd9Zi6dlyEy_ncdOm_IxsGlULe8o5qJNFgE5x_9SWmpzGp9N2-MXQxz4c2cOQ-lZWQyF0Jd2q_-mjI9U1fr4FBPE8iuKTbjjRt2sMBK0svIsQtG6jb2CsQAdQ_1x9f5R9tmIS-yPToK-tNkQRQGL6ObCIIdEpH9wQ3p-Enk0LEGXwe4ktoX2hhFai5Ofi0jPnYc9QF1LrDdRK-rvXjerSfNitQ_TlqeBc1hwRi7yY3F81MnK9KtsF2n8Amis44ilA7VtwfWTyr-kaKV-_X4cH8BTOhfRzcEAAA=)): ```diff <Table data={fruits}> @@ -215,14 +215,14 @@ Any content inside the component tags that is _not_ a snippet declaration implic </table> ``` -> Note that you cannot have a prop called `children` if you also have content inside the component — for this reason, you should avoid having props with that name +> コンポーネントの内側にコンテンツがある場合、`children` という名前の props を持つことはできません。このため、`children` という名前の props は避けるべきです。 ## Snippets and slots -In Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/special-elements#slot). Snippets are more powerful and flexible, and as such slots are deprecated in Svelte 5. +Svelte 4 では、コンポーネントにコンテンツを渡すのに [slot](https://svelte.jp/docs/special-elements#slot) を使用します。snippet はよりパワフルで柔軟であるため、slot は Svelte 5 では非推奨です。 -They continue to work, however, and you can mix and match snippets and slots in your components. +しかし、まだ slot を使用することはでき、コンポーネントで snippet と slot を組み合わせることができます。 ## Typing snippets -Right now, it's not possible to add types for snippets and their parameters. This is something we hope to address before we ship Svelte 5. +現時点では、snippet とそのパラメータに型を付けることはできません。Svelte 5 をリリースする前に取り組みたいと考えています。 diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md index 937026e5a..af724a0a5 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md @@ -2,7 +2,7 @@ title: Event handlers --- -Event handlers have been given a facelift in Svelte 5. Whereas in Svelte 4 we use the `on:` directive to attach an event listener to an element, in Svelte 5 they are properties like any other: +Svelte 5 ではイベントハンドラが刷新されました。Svelte 4 では要素にイベントリスナーをアタッチするのに `on:` ディレクティブを使用していましたが、Svelte 5 では他のプロパティと同じように扱うことができます: ```diff <script> @@ -15,7 +15,7 @@ Event handlers have been given a facelift in Svelte 5. Whereas in Svelte 4 we us </button> ``` -Since they're just properties, you can use the normal shorthand syntax... +ただのプロパティとして扱えるため、おなじみの短縮構文(shorthand syntax)を使うことができます… ```svelte <script> @@ -31,15 +31,15 @@ Since they're just properties, you can use the normal shorthand syntax... </button> ``` -...though when using a named event handler function it's usually better to use a more descriptive name. +…ただし、名前付きのイベントハンドラ関数を使用する場合は、より明確な名前を使用したほうがよいでしょう。 -Traditional `on:` event handlers will continue to work, but are deprecated in Svelte 5. +従来の `on:` イベントハンドラも使用することができますが、Svelte 5 では非推奨となります。 ## Component events -In Svelte 4, components could emit events by creating a dispatcher with [`createEventDispatcher`](https://svelte.dev/docs/svelte#createeventdispatcher). +Svelte 4 では、[`createEventDispatcher`](https://svelte.jp/docs/svelte#createeventdispatcher) を使用して dispatcher を作成すること、コンポーネントはイベントを発行していました。 -This function is deprecated in Svelte 5. Instead, components should accept _callback props_ ([demo](/#H4sIAAAAAAAAE41TS27bMBC9ykBtELu1ZTmAG0C2hPYG3dddyPIwJkKRAjmy4wrad9VFL5BV75cjlKQof5osutCHb968-XCmjRgXaKL0WxvJosIojb7UdTSJ6Fi7g9mjILRnoxpdOmRlSs1rytdyTbyqlSb42lQ1MK0quI1n7hD3brdLR3KPQALDfyBk8N5QQTiaL8bLwbJptKGziRXCoLdaO2tkSVxJ0GiQRmNovSYFtfmij0GDhnf2WLeWq9k5WblymfmsJRM2TtZatSy_EvyYwSDIGYw8lsP9YnzKkXQT5Dv33uJbWhe-ybgvfDooO7-ZT6h9Z3le10utNg2RLVTJUvDyMWt9xV0u8QCbQgilbD09xzd_ZepCQikKY7J1tFGqWkf5y_PvP7Zqa7GcNkXbjO4Nci-3jsDQUaBFTFkITKFN4mQOH3zKnZXry3l5_vXTi5yEZ5x1vqfe39N8gFB_rQx3l5YC40-4DR0VyCiFJJxI1efDgW9pl8I8SW4CskP-sKMriClJU5eZR_eHQQifaFoI_mDDlSgJ9RCPS5yedJZDatxRpri3VJOCVPI0Lu4Th94MpZAu5FCMbxIk8Z259rCtH-iF5FXRsz2cxAsDTOlDobdXXp8f8ci03TgDl_7JDbQQLiOJP0HXw3eLK_x-MRhcey4sPdxPfrgZu7uV2nLGcRulbnq7yWnV3Ub87667RW0h7M4EwuBD5_a21qo2I7ey1xv370QH7y4PPxfz_IobAnR5-DlxXxf0vfsLb_4Z08cEAAA=)): +Svelte 5 では `createEventDispatcher` は非推奨となります。代わりに、コンポーネントで _コールバック props_ を受け取ります ([デモ](/#H4sIAAAAAAAAE41TS27bMBC9ykBtELu1ZTmAG0C2hPYG3dddyPIwJkKRAjmy4wrad9VFL5BV75cjlKQof5osutCHb968-XCmjRgXaKL0WxvJosIojb7UdTSJ6Fi7g9mjILRnoxpdOmRlSs1rytdyTbyqlSb42lQ1MK0quI1n7hD3brdLR3KPQALDfyBk8N5QQTiaL8bLwbJptKGziRXCoLdaO2tkSVxJ0GiQRmNovSYFtfmij0GDhnf2WLeWq9k5WblymfmsJRM2TtZatSy_EvyYwSDIGYw8lsP9YnzKkXQT5Dv33uJbWhe-ybgvfDooO7-ZT6h9Z3le10utNg2RLVTJUvDyMWt9xV0u8QCbQgilbD09xzd_ZepCQikKY7J1tFGqWkf5y_PvP7Zqa7GcNkXbjO4Nci-3jsDQUaBFTFkITKFN4mQOH3zKnZXry3l5_vXTi5yEZ5x1vqfe39N8gFB_rQx3l5YC40-4DR0VyCiFJJxI1efDgW9pl8I8SW4CskP-sKMriClJU5eZR_eHQQifaFoI_mDDlSgJ9RCPS5yedJZDatxRpri3VJOCVPI0Lu4Th94MpZAu5FCMbxIk8Z259rCtH-iF5FXRsz2cxAsDTOlDobdXXp8f8ci03TgDl_7JDbQQLiOJP0HXw3eLK_x-MRhcey4sPdxPfrgZu7uV2nLGcRulbnq7yWnV3Ub87667RW0h7M4EwuBD5_a21qo2I7ey1xv370QH7y4PPxfz_IobAnR5-DlxXxf0vfsLb_4Z08cEAAA=)): ```svelte <script> @@ -85,7 +85,7 @@ This function is deprecated in Svelte 5. Instead, components should accept _call ## Bubbling events -Instead of doing `<button on:click>` to 'forward' the event from the element to the component, the component should accept an `onclick` callback prop: +要素からイベントを 'forward' するのに `<button on:click>` を使用する代わりに、コンポーネントで `onclick` コールバック prop を受け取りましょう: ```svelte <script> @@ -97,7 +97,7 @@ Instead of doing `<button on:click>` to 'forward' the event from the element to </button> ``` -Note that this also means you can 'spread' event handlers onto the element along with other props: +これはつまり、要素に対してイベントハンドラを他の props と一緒に 'spread' することができるということです: ```svelte <script> @@ -111,15 +111,15 @@ Note that this also means you can 'spread' event handlers onto the element along ## Event modifiers -In Svelte 4, you can add event modifiers to handlers: +Svelte 4 では、イベントの修飾子 (modifier) をハンドラに付けることができます: ```svelte <button on:click|once|preventDefault={handler}>...</button> ``` -Modifiers are specific to `on:` and as such do not work with modern event handlers. Adding things like `event.preventDefault()` inside the handler itself is preferable, since all the logic lives in one place rather than being split between handler and modifiers. +修飾子は `on:` に固有のもので、このモダンなイベントハンドラでは動作しません。`event.preventDefault()` などはハンドラ自体の内側に追加することが望ましいです。なぜなら全てのロジックがハンドラと修飾子の間で分割されるのではなく、1つの場所に存在するからです。 -Since event handlers are just functions, you can create your own wrappers as necessary: +イベントハンドラはただの関数なので、必要に応じてラッパーを作成することができます: ```svelte <script> @@ -141,27 +141,27 @@ Since event handlers are just functions, you can create your own wrappers as nec <button onclick={once(preventDefault(handler))}>...</button> ``` -There are three modifiers — `capture`, `passive` and `nonpassive` — that can't be expressed as wrapper functions, since they need to be applied when the event handler is bound rather than when it runs. +3つの修飾子 — `capture`、`passive`、`nonpassive` — は、イベントハンドラが実行されるときではなく、イベントハンドラがバインドされるときに適用する必要があるため、ラッパー関数で表現することができません。 -For `capture`, we add the modifier to the event name: +`capture` の場合は、イベント名に修飾子を追加します: ```svelte <button onclickcapture={...}>...</button> ``` -Changing the [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) option of an event handler, meanwhile, is not something to be done lightly. If you have a use case for it — and you probably don't! - then you will need to use an action to apply the event handler yourself. +一方、イベントハンドラの [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) オプションを変更することは、安易に行ってよいことではありません。もしそのようなユースケースがあれば (おそらくないとは思いますが)、ご自身でイベントハンドラに適用する action を使用する必要があります。 ## Multiple event handlers -In Svelte 4, this is possible: +Svelte 4 では、以下のようにすることができました: ```svelte <button on:click={one} on:click={two}>...</button> ``` -This is something of an anti-pattern, since it impedes readability (if there are many attributes, it becomes harder to spot that there are two handlers unless they are right next to each other) and implies that the two handlers are independent, when in fact something like `event.stopImmediatePropagation()` inside `one` would prevent `two` from being called. +これはアンチパターンの一種で、可読性が悪くなり (もし属性が多い場合、隣同士に配置しないと2つのハンドラがあることに気が付くことが難しくなる)、また、実際には `one` の中に `event.stopImmediatePropagation()` などがあれば `two` が呼び出されるのを止めることができるのに、2つのハンドラが独立しているように見えてしまいます。 -Duplicate attributes/properties on elements — which now includes event handlers — are not allowed. Instead, do this: +イベントハンドラも含め、要素の属性/プロパティが重複することは許されません。代わりに、以下のようにしてください: ```svelte <button @@ -176,12 +176,12 @@ Duplicate attributes/properties on elements — which now includes event handler ## Why the change? -By deprecating `createEventDispatcher` and the `on:` directive in favour of callback props and normal element properties, we: +`createEventDispatcher` と `on:` ディレクティブを非推奨にしてコールバック props と通常の要素プロパティを使用することによって: -- reduce Svelte's learning curve -- remove boilerplate, particularly around `createEventDispatcher` -- remove the overhead of creating `CustomEvent` objects for events that may not even have listeners -- add the ability to spread event handlers -- add the ability to know which event handlers were provided to a component -- add the ability to express whether a given event handler is required or optional -- increase type safety (previously, it was effectively impossible for Svelte to guarantee that a component didn't emit a particular event) +- Svelte の学習曲線を短くします +- ボイラープレートを削除します (特に `createEventDispatcher` 周り) +- リスナーが存在しない可能性があるイベントのための `CustomEvent` オブジェクトを作成することによるオーバーヘッドを削除します +- イベントハンドラを spread できるようにします +- どのイベントハンドラがコンポーネントに提供されたかわかるようにします +- 提供されたイベントハンドラが必須かオプションか表現できるようにします +- 型安全性を強化します (以前は、あるコンポーネントが特定のイベントを発行しないことを Svelte が保証することは事実上不可能でした) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-functions.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-functions.md index b529d05cf..a87c30f24 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-functions.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-functions.md @@ -2,11 +2,11 @@ title: Functions --- -As well as runes, Svelte 5 will introduce a couple of new functions, in addition to existing functions like `getContext`, `setContext` and `tick`. These are introduced as functions rather than runes because they are used directly and the compiler does not need to touch them to make them function as it does with runes. However, these functions may still use Svelte internals. +rune だけでなく、Svelte 5 では `getContext`、`setContext`、`tick` などの既存の関数に加えていくつか新しい関数が導入されます。これらの関数は、rune のようにコンパイラが触れる必要はなく、直接使用できるため、rune としてではなく関数として導入されます。しかし、これらの関数が Svelte の内部を使用する可能性はあります。 ## `untrack` -To prevent something from being treated as an `$effect`/`$derived` dependency, use `untrack`: +`$effect`/`$derived` の依存(dependency)として扱われるのを防ぐには、`untrack` を使用します: ```svelte <script> diff --git a/sites/svelte-5-preview/src/routes/docs/content/02-examples/01-universal-reactivity.md b/sites/svelte-5-preview/src/routes/docs/content/02-examples/01-universal-reactivity.md index 0ce95dc7a..97c11cc1e 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/02-examples/01-universal-reactivity.md +++ b/sites/svelte-5-preview/src/routes/docs/content/02-examples/01-universal-reactivity.md @@ -2,9 +2,9 @@ title: Universal reactivity --- -In Svelte 5, you can create reactive state anywhere in your app — not just at the top level of your components. +Svelte 5 ではリアクティブな state を、コンポーネントのトップレベルだけでなくアプリのどこにでも作成することができます。 -Suppose we have a component like this: +以下のようなコンポーネントがあるとします: ```svelte <script> @@ -20,7 +20,7 @@ Suppose we have a component like this: </button> ``` -We can encapsulate this logic in a function, so that it can be used in multiple places: +このロジックを関数にカプセル化することで、複数の場所で使用できるようになります: ```diff <script> @@ -47,9 +47,9 @@ We can encapsulate this logic in a function, so that it can be used in multiple </button> ``` -> Note that we're using a [`get` property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) in the returned object, so that `counter.count` always refers to the current value rather than the value at the time the `createCounter` function was called. +> 戻り値のオブジェクトで [`get` プロパティ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) を使用していることに注目してください。これによって `counter.count` は、`createCounter` 関数が呼ばれたときの値ではなく、常に現在の値を参照します。 -We can also extract that function out into a separate `.svelte.js` or `.svelte.ts` module... +また、この関数を別の `.svelte.js` (または `.svelte.ts`) モジュールに抽出することもできます… ```js export function createCounter() { @@ -68,7 +68,7 @@ export function createCounter() { } ``` -...and import it into our component: +…そしてコンポーネントでインポートすると: ```diff <script> @@ -83,11 +83,11 @@ export function createCounter() { </button> ``` -[See this example in the playground.](/#H4sIAAAAAAAACmVQ0U7DMAz8FStC2iaqDl67dhLiMxgPI3NRRutUiYNAVf6dJG1TBk-W7bvznUfRqg6tqF5GQeceRSWehkEUgr-H2NhP7BhDb7UzMk5qK40a-HiiE6t-0IZhBGnwzPisHTEa8NAa3cOm3MtpUk4y5dVuDoEXmFKTZZjX0NwKbHcBVe_XQ1S_OWZNoKmSnZIfzbgoKwrUHol9cpS2toK8T9VHuUniGLL0-qJahRdRsXHoixz91u76hav9_QH8SqlbR5JVMPXHO4zRSIdzvBDuznIAbB92c_jMzOYXVnxM5Nw38BjB0XksBtkZWjDvi_ZKy5A0P0xDX0w1n0mKYen_P-HV_wBwv1jcCwIAAA==) +[この例を playground でご覧ください。](/#H4sIAAAAAAAACmVQ0U7DMAz8FStC2iaqDl67dhLiMxgPI3NRRutUiYNAVf6dJG1TBk-W7bvznUfRqg6tqF5GQeceRSWehkEUgr-H2NhP7BhDb7UzMk5qK40a-HiiE6t-0IZhBGnwzPisHTEa8NAa3cOm3MtpUk4y5dVuDoEXmFKTZZjX0NwKbHcBVe_XQ1S_OWZNoKmSnZIfzbgoKwrUHol9cpS2toK8T9VHuUniGLL0-qJahRdRsXHoixz91u76hav9_QH8SqlbR5JVMPXHO4zRSIdzvBDuznIAbB92c_jMzOYXVnxM5Nw38BjB0XksBtkZWjDvi_ZKy5A0P0xDX0w1n0mKYen_P-HV_wBwv1jcCwIAAA==) ## Stores equivalent -In Svelte 4, the way you'd do this is by creating a [custom store](https://learn.svelte.dev/tutorial/custom-stores), perhaps like this: +Svelte 4 でこれを行うには、[カスタムの store](https://learn.svelte.jp/tutorial/custom-stores) を作成します。以下のようになるでしょう: ```js import { writable } from 'svelte/store'; @@ -106,7 +106,7 @@ export function createCounter() { } ``` -Back in the component, we retrieve the store value by prefixing its name with `$`: +コンポーネントに戻り、store に `$` プリフィクスをつけて store の値を取得します: ```diff <script> @@ -121,13 +121,13 @@ Back in the component, we retrieve the store value by prefixing its name with `$ </button> ``` -The store approach has some significant drawbacks. A counter is just about the simplest custom store we could create, and yet we have to completely change how the code is written — importing `writable`, understanding its API, grabbing references to `subscribe` and `update`, changing the implementation of `increment` from `count += 1` to something far more cryptic, and prefixing the store name with a `$` to retrieve its value. That's a lot of stuff you need to understand. +store のアプローチには重大な欠点がありました。counter は最もシンプルなカスタム store ですが、それでもコードの書き方を完全に変更する必要があります — `writable` をインポートし、その API を理解し、`subscribe` と `update` の参照を取得し、`increment` の実装を `count += 1` からよりわかりにくいものに変更し、store の名前に `$` プリフィクスを付けて値を取得します。理解しなければいけないことがとても多いです。 -With runes, we just copy the existing code into a new function. +rune では、既存のコードをコピーして新しい関数にするだけです。 ## Gotchas -Reactivity doesn't magically cross function boundaries. In other words, replacing the `get` property with a regular property wouldn't work... +リアクティビティは魔法のように関数の境界を越えることはありません。言い換えると、`get` プロパティから通常のプロパティに置き換えると動作しなくなるということです… ```diff export function createCounter() { @@ -145,4 +145,4 @@ export function createCounter() { } ``` -...because the value of `count` in the returned object would always be `0`. Using the `$state` rune doesn't change that fact — it simply means that when you _do_ read `count` (whether via a `get` property or a normal function) inside your template or inside an effect, Svelte knows what to update when `count` changes. +…なぜなら戻り値のオブジェクトの `count` の値は常に `0` だからです。`$state` rune を使用してもその事実は代わりません — 単純に、テンプレートや effect の内側で `count` を読み取るときに (`get` プロパティ経由であれ、通常の関数経由であれ)、Svelte は `count` が変更されたときに何を更新すべきかを把握している、というだけです。 diff --git a/sites/svelte-5-preview/src/routes/docs/content/02-examples/02-fine-grained-reactivity.md b/sites/svelte-5-preview/src/routes/docs/content/02-examples/02-fine-grained-reactivity.md index efc692f70..31ab7a615 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/02-examples/02-fine-grained-reactivity.md +++ b/sites/svelte-5-preview/src/routes/docs/content/02-examples/02-fine-grained-reactivity.md @@ -3,6 +3,7 @@ title: Fine-grained reactivity --- In Svelte 4, reactivity centres on the _component_ and the top-level state declared therein. What this means is that in a situation like this... +Svelte 4 では、リアクティビティは _コンポーネント_ とそのトップレベルに宣言された state が中心でした。つまり、以下のような状況では… ```svelte <script> @@ -40,26 +41,17 @@ In Svelte 4, reactivity centres on the _component_ and the top-level state decla <p>{remaining(todos)} remaining</p> ``` -...editing any individual `todo` will invalidate the entire list. You can see this for yourself by [opening the playground](/#H4sIAAAAAAAAE2VSy27jMAz8FVV7cAIE8t21DfSwf7C3OgdVohOhCmXIdLaF4H9fPewE6N7I0ZAzpBj4aCzMvHkPHOUNeMPfpomfOH1PKZnvYAliPrvFq4S0s_Jmon7AgSwQI6fdzDr2fn6NUATHBRUZh8zDTRo0eDlkzpGF9DyQcjg7C8K6y6HyoKRVi5UUidXxtVA80OKx9BbRIYHPTVjXs5cUCO0QjsICXuiai9Yf6lLrP5F4gDsgPbTNyAoiPuGbvXQdq35j7F4dWdHchhjoMVdJBxJCZOy0A2EPBkpuGjZKO8PpiRJ8UcOKHEl_ARJ3aRfYGWsJzg_N_6nRQFXt87X1c_fYGpwWYg6bOIl2f7EL28grqzMj_AKprtsHyTkHWbLV5t4Xxa3Lh0HdZMEu5PUm61ufJyvdRDdwdQX1-eG-Bl7qcg56q0yr2CvbuiiFOjnJP9ROffh5GOvzVNp66uO13Zw2owHNG_ILrOf1H3DaaQeoAgAA), adding some todos, and watching the console in the bottom right. `remaining(todos)` is recalculated every time we edit the `text` of a todo, even though it can't possibly affect the result. +…個別の `todo` を編集するとリスト全体が無効化/最新化(invalidate)されます。この挙動を確認するには、[playground を開き](/#H4sIAAAAAAAAE2VSy27jMAz8FVV7cAIE8t21DfSwf7C3OgdVohOhCmXIdLaF4H9fPewE6N7I0ZAzpBj4aCzMvHkPHOUNeMPfpomfOH1PKZnvYAliPrvFq4S0s_Jmon7AgSwQI6fdzDr2fn6NUATHBRUZh8zDTRo0eDlkzpGF9DyQcjg7C8K6y6HyoKRVi5UUidXxtVA80OKx9BbRIYHPTVjXs5cUCO0QjsICXuiai9Yf6lLrP5F4gDsgPbTNyAoiPuGbvXQdq35j7F4dWdHchhjoMVdJBxJCZOy0A2EPBkpuGjZKO8PpiRJ8UcOKHEl_ARJ3aRfYGWsJzg_N_6nRQFXt87X1c_fYGpwWYg6bOIl2f7EL28grqzMj_AKprtsHyTkHWbLV5t4Xxa3Lh0HdZMEu5PUm61ufJyvdRDdwdQX1-eG-Bl7qcg56q0yr2CvbuiiFOjnJP9ROffh5GOvzVNp66uO13Zw2owHNG_ILrOf1H3DaaQeoAgAA)、todo を追加し、右下のコンソールを見てみてください。`remaining(todos)` は todo の `text` を編集するたびに再計算されます (結果には影響しないにも関わらず)。 -Worse, everything inside the `each` block needs to be checked for updates. When a list gets large enough, this behaviour has the potential to cause performance headaches. +さらに良くないことに、`each` ブロックの内側では全て更新をチェックする必要があります。リストがかなり大きくなると、この動作がパフォーマンスの頭痛の種になる可能性があります。 -With runes, it's easy to make reactivity _fine-grained_, meaning that things will only update when they need to. Mark `todos` as `$state`, create a `Todo` class with `done` and `text` state fields, then instantiate the class inside `addTodo`: +rune を使用すると、リアクティビティを _きめ細やか(fine-grained)_ にすることができます。つまり、必要なときに必要なものだけ更新されるようになります: ```diff <script> - let todos = []; + let todos = $state([]); -+ class Todo { -+ done = $state(false); -+ text = $state(); -+ -+ constructor(text) { -+ this.text = text; -+ } -+ } - function remaining(todos) { console.log('recalculating'); return todos.filter(todo => !todo.done).length; @@ -75,19 +67,14 @@ With runes, it's easy to make reactivity _fine-grained_, meaning that things wil - text: event.target.value - } - ]; -+ todos = [...todos, new Todo(event.target.value)]; ++ todos.push({ ++ done: false, ++ text: event.target.value ++ }); event.target.value = ''; } </script> ``` -In [this version of the app](/#H4sIAAAAAAAAE21SwW6DMAz9lSybBEhTuDNA2mF_sFvpIUtMGzVNUGK6TYh_XxKgSNtOsWO_Z_vZE-2VBk-rw0QNvwKt6Osw0GeK30N0_A00QvC9HZ2IP7UXTg3YdqZDDUjQSutJQ548coT8cCxeQigEhebek_cQJlP0O5TWwJ7Zc-0hJYcQwhfuoY0ikFjj0Y0CrctjTrFxBchZebbi4rMyzfGZF3w_GoHKGuLgypVR5pSndu8skd5qYNqe8syB4FqMmmNIzLbOHODozDImC2IhuERCmpY8RIPFsQqmwZzw_PJfdS5llCGHG5h9AtWT5Ydd4Js8NA3J3kxgzwqy1LyLsEl8YIwl-5kY-CQ7J0PuToDsxvUIxfEO_BsMLFm2NVmX-y5NrcwwIrGmCu1I-2maae17JmXKmB6Bi_O6cO6TkdSupbq1S8WV5UMZWaWCzZQ0igtaefaseGNNR8UZxOXDfnV0wSUf5IqM6m7IulwqTWXsJMlcD-30e7vzvu-6HNpwvVcrVa9A0iocE8zH-QeS_FSn-AIAAA==), editing the `text` of a todo won't cause unrelated things to be updated. - -## Gotchas - -If we only do the first step (adding `$state`) and skip the second (creating a class with state fields), [the app breaks](/#H4sIAAAAAAAAE2VSyU7DMBD9FWOQ0kqVcw9JJA78AbemB2NPWgvXjuxJAVn5d7wkVILbLG_mvVkCHZUGT5tjoIZfgTb0ZZrogeL3lBx_A40QfW9nJ1Kk9cKpCfvBDKgBCVppPenIk0eOsDue9s8xFZPjbAQqa4iDK1dGmfMuY_ckpPSAwhpvNTBtz7vKgeBazJpjBFa5R4Q4wNmZwsGiUgSXm5CuJw_JYNIa2DMN5oyXXLT8YedSvkXgDm5g8JdbjaRE2Ad8k4euI9Wrid2rPSmc6xADbvMdizsgYyzHDlsgbMaASU1DRq49HO5RhC9sSKFD7s6A7Mb1DBtiKcbpl_M_NAqoqm2-tr7fwLTKTDMSa5o4ibSfpgvryAupMyI8AheX9VDcZyNTtlLd-sK4dnlXRjaZsAt5vUn62ueOSr_RDVRcQHy826-Blrrsg1wr0yq2yrYuTKFOSvKF2qkPfx9jub9KW099_LqrlWpUIGmDbobltPwAmGXpQrACAAA=) — toggling the checkboxes won't cause `remaining(todos)` to be recalculated. - -That's because in runes mode, Svelte no longer invalidates everything when you change something inside an `each` block. Previously, Svelte tried to statically determine the dependencies of the mutated value in order to invalidate them, causing confusing bugs related to overfiring (invalidating things that weren't actually affected) and underfiring (missing affected variables). It made apps slower by default and harder to reason about, especially in more complex scenarios. - -In runes mode, the rules around triggering updates are simpler: Only state declared with `$state` or `$derived` or `$props` causes a rerender. In the [broken example](/#H4sIAAAAAAAAE2VSyU7DMBD9FWOQ0kqVcw9JJA78AbemB2NPWgvXjuxJAVn5d7wkVILbLG_mvVkCHZUGT5tjoIZfgTb0ZZrogeL3lBx_A40QfW9nJ1Kk9cKpCfvBDKgBCVppPenIk0eOsDue9s8xFZPjbAQqa4iDK1dGmfMuY_ckpPSAwhpvNTBtz7vKgeBazJpjBFa5R4Q4wNmZwsGiUgSXm5CuJw_JYNIa2DMN5oyXXLT8YedSvkXgDm5g8JdbjaRE2Ad8k4euI9Wrid2rPSmc6xADbvMdizsgYyzHDlsgbMaASU1DRq49HO5RhC9sSKFD7s6A7Mb1DBtiKcbpl_M_NAqoqm2-tr7fwLTKTDMSa5o4ibSfpgvryAupMyI8AheX9VDcZyNTtlLd-sK4dnlXRjaZsAt5vUn62ueOSr_RDVRcQHy826-Blrrsg1wr0yq2yrYuTKFOSvKF2qkPfx9jub9KW099_LqrlWpUIGmDbobltPwAmGXpQrACAAA=), `todo` is declared by the `#each` block, and neither the `text` nor the `done` property are referencing state. One solution would be to turn `text` and `done` into `$state` fields, as shown above. [The other solution](/#H4sIAAAAAAAACmVS226jMBD9lam7EokUmXcKSH3YP9i3EK1ce0isOjayh7QV4t9rG2iq9m0uZ86Z28R6bTCw6jgxK67IKvY8DOzA6GNITrihIYx-cKOXKVIH6fVAbWc7MkhATrkADfwJJAh3x9P-KaZish-tJO0seLwKbbU97zJ2D1NKdySdDc4gN-68KzxKYeRoBEVgkTkixCON3i4aPHZK6DMJNC08JIMrZ3HPDdozXXLR_ENdKPUvAnd4Q0tf2rqHJcJf8QMemgaKvzayF3tYNNchOtrmO3LOs33YODpK4hX0wgQ8bDHCd6pg4Sbhz0j8JsyIS34-fRH_hkSVotiGqMv7om2t7TASOFvFdpV7s820zjVDmRHTIwp5Wa8hAvw_gE6roFrpW7soriwv2qoqCzZTxh_1iae2V647Mj1B0zF5Qfn64t47ttRmH9W36rSIrbouF8WpTB3lc9RDO_38gvn-F3U5tPHFrk7pXqNiFfkR59P8CWtDxuCdAgAA) would be to bind to `todos[i].text` instead of `todo.text` — this way, Svelte picks up the reference to the `todos` `$state` and invalidates it as a whole. Keep in mind that you lose the fine-grained reactivity this way — the whole array is invalidated on every keystroke. +[アプリのこのバージョン](/#H4sIAAAAAAAAE2VSy07EMAz8lRCQ2kqovZe2Egf-gBvlEBJ3N9qsUyXuAqr67-TRZSW4xfZ4xh5n5ZM24Hn7tnIUZ-Atf55n_sjpe46Bv4AhCLG3i5Mx03np9EzDiCMZIEZWWc969uBJEJRv79VTKIXitKAkbZE5OAuNGg9lwlZsjeWRpEVvDdTGHsrCgRRGLkZQABaJI0Ac0OIwa9RhUgKXSFg_sLv4qJVFqGoDeKBjatr-qAulXgOwhAsg_WrrieVMfYJvdtf3rHjBwF5ULGvuS4yUtefFH8u9d6Qo2rJJGA-P1xzBF7Usc5JwB6D6IswCub5Vv4T_IcG9orgO3zU3g7HTOC_ELLZhTGU_sV_3fTbWJMR6D0Ie9ysInx7RAuqUvgxZcWf50KjaJNivybs48s5zQ8XD9yOXR5CnD_s18tyXYlB7ZzTg2tk1WWlt4iTJ_m4e1r9X327_oGvmIXyps1V60qB4S26B7X37AXGd34ONAgAA)では、todo の `text` を編集しても関係ない部分は更新されません。 diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md index 26ae9903e..a92d5d68d 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md @@ -9,17 +9,23 @@ While Svelte 5 is a complete rewrite, we have done our best to ensure that most In Svelte 3 and 4, components are classes. In Svelte 5 they are functions and should be instantiated differently. If you need to manually instantiate components, you should use `mount` or `createRoot` (imported from `svelte`) instead. If you see this error using SvelteKit, try updating to the latest version of SvelteKit first, which adds support for Svelte 5. If you're using Svelte without SvelteKit, you'll likely have a `main.js` file (or similar) which you need to adjust: ```diff -+ import { mount } from 'svelte'; ++ import { createRoot } from 'svelte'; import App from './App.svelte' - const app = new App({ target: document.getElementById("app") }); -+ const app = mount(App, { target: document.getElementById("app") }); ++ const app = createRoot(App, { target: document.getElementById("app") }); export default app; ``` +`createRoot` returns an object with a `$set` and `$destroy` method on it. It does not come with an `$on` method you may know from the class component API. Instead, pass them via the `events` property on the options argument. If you don't need to interact with the component instance after creating it, you can use `mount` instead, which saves some bytes. + +> Note that using `events` is discouraged — instead, [use callbacks](https://svelte-5-preview.vercel.app/docs/event-handlers) + As a stop-gap-solution, you can also use `createClassComponent` or `asClassComponent` (imported from `svelte/legacy`) instead to keep the same API after instantiating. If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility (note that this adds a bit of overhead to each component). +### Server API changes + Similarly, components no longer have a `render` method when compiled for server side rendering. Instead, pass the function to `render` from `svelte/server`: ```diff @@ -32,6 +38,10 @@ import App from './App.svelte'; `render` also no longer returns CSS; it should be served separately from a CSS file. +### bind:this changes + +Because components are no longer classes, using `bind:this` no longer returns a class instance with `$set`, `$on` and `$destroy` methods on it. It only returns the instance exports (`export function/const`) and, if you're using the `accessors` option, a getter/setter-pair for each property. + ## Whitespace handling changed Previously, Svelte employed a very complicated algorithm to determine if whitespace should be kept or not. Svelte 5 simplifies this which makes it easier to reason about as a developer. The rules are: @@ -64,10 +74,22 @@ Content inside component tags becomes a [snippet prop](/docs/snippets) called `c Assignments to destructured parts of a `@const` declaration are no longer allowed. It was an oversight that this was ever allowed. +### Stricter CSS `:global` selector validation + +Previously, a selector like `.foo :global(bar).baz` was valid. In Svelte 5, this is a validation error instead. The reason is that in this selector the resulting CSS would be equivalent to one without `:global` - in other words, `:global` is ignored in this case. + ### CSS hash position no longer deterministic Previously Svelte would always insert the CSS hash last. This is no longer guaranteed in Svelte 5. This is only breaking if you [have very weird css selectors](https://stackoverflow.com/questions/15670631/does-the-order-of-classes-listed-on-an-item-affect-the-css). +### Renames of various error/warning codes + +Various error and warning codes have been renamed slightly. + +### Reduced number of namespaces + +The number of valid namespaces you can pass to the compiler option `namespace` has been reduced to `html` (the default), `svg` and `foreign`. + ### beforeUpdate change `beforeUpdate` no longer runs twice on initial render if it modifies a variable referenced in the template. diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/03-deprecations.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/03-deprecations.md index 6ffd8a957..b498862c4 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/03-deprecations.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/03-deprecations.md @@ -39,3 +39,7 @@ These functions run indiscriminately when _anything_ changes. By using `$effect. ``` Note that using `$effect` and `$effect.pre` will put you in [runes mode](/docs/runes) — be sure to update your props and state accordingly. + +## `immutable` + +The `immutable` compiler option is deprecated. Use runes mode instead, where all state is immutable (which means that assigning to `object.property` won't cause updates for anything that is observing `object` itself, or a different property of it). diff --git a/sites/svelte.dev/package.json b/sites/svelte.dev/package.json index 6ee8e7101..3dc199734 100644 --- a/sites/svelte.dev/package.json +++ b/sites/svelte.dev/package.json @@ -13,8 +13,8 @@ "start": "node build", "check": "node scripts/update.js && pnpm generate && svelte-kit sync && svelte-check", "check:watch": "svelte-kit sync && svelte-check --watch", - "format": "prettier --check . --ignore-path .gitignore --plugin-search-dir=. --write", - "check:format": "prettier --check . --ignore-path .gitignore --plugin-search-dir=." + "format": "prettier --check . --write", + "check:format": "prettier --check ." }, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", @@ -42,8 +42,8 @@ "lightningcss": "^1.21.8", "magic-string": "^0.30.3", "marked": "^9.0.0", - "prettier": "^3.0.3", - "prettier-plugin-svelte": "^3.0.3", + "prettier": "^3.1.0", + "prettier-plugin-svelte": "^3.1.2", "sass": "^1.67.0", "satori": "^0.10.4", "satori-html": "^0.3.2", diff --git a/sites/svelte.dev/src/routes/+layout.svelte b/sites/svelte.dev/src/routes/+layout.svelte index 36061390b..0fe9457f6 100644 --- a/sites/svelte.dev/src/routes/+layout.svelte +++ b/sites/svelte.dev/src/routes/+layout.svelte @@ -32,11 +32,7 @@ </svelte:head> <div style:display={$page.url.pathname !== '/docs' ? 'contents' : 'none'}> - <Shell - nav_visible={$page.url.pathname !== '/repl/embed'} - bind:snapshot={shell_snapshot} - banner_bottom_height="42px" - > + <Shell nav_visible={$page.url.pathname !== '/repl/embed'} bind:snapshot={shell_snapshot}> <Nav slot="top-nav" title={data.nav_title} links={data.nav_links}> <svelte:fragment slot="home-large"> <strong>svelte</strong>.dev @@ -72,12 +68,6 @@ </Nav> <slot /> - - <div slot="banner-bottom" class="banner-bottom"> - <a href="https://www.sveltesummit.com/2023/fall" class="banner-bottom" - >Join us at Svelte Summit on Nov 11</a - > - </div> </Shell> </div> @@ -94,12 +84,4 @@ height: 100%; width: 100%; } - - .banner-bottom { - text-align: center; - background: var(--sk-theme-1-variant); - color: white; - text-decoration: underline; - padding: 8px; - } </style>