From c53e26f2d04b180435055f9457becce8311e421f Mon Sep 17 00:00:00 2001 From: Dennis Kamau <97116157+devcamke@users.noreply.github.com> Date: Mon, 1 Jan 2024 20:37:35 +0300 Subject: [PATCH 01/10] chore: update copyright year to include 2024 (#10047) --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index aa74406768..e2a8b89fa4 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2016-23 [these people](https://github.com/sveltejs/svelte/graphs/contributors) +Copyright (c) 2016-24 [these people](https://github.com/sveltejs/svelte/graphs/contributors) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From 60575ad494e165ed3c26c679fc087e43b859e340 Mon Sep 17 00:00:00 2001 From: Jason Chase Date: Mon, 1 Jan 2024 15:11:28 -0500 Subject: [PATCH 02/10] docs: fix typo in snippets doc (#10030) "Declare" should be "Declared" (past tense) --- .../src/routes/docs/content/01-api/03-snippets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a1114007d1..b227a9a9d0 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 @@ -156,7 +156,7 @@ Within the template, snippets are values just like any other. As such, they can ``` -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=)): +As an authoring convenience, snippets declared 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=)): ```svelte From 346041f4547b6064e1b024c250403f43271c1e71 Mon Sep 17 00:00:00 2001 From: Jeremy Deutsch Date: Tue, 2 Jan 2024 01:26:20 -0800 Subject: [PATCH 03/10] chore: skip generating $.proxy() calls for more expressions (#9979) * chore: skip emitting $.proxy() calls for more expressions * Update pretty-ties-help.md --------- Co-authored-by: Dominic Gannaway --- .changeset/pretty-ties-help.md | 5 +++++ .../svelte/src/compiler/phases/3-transform/client/utils.js | 2 ++ .../_expected/client/index.svelte.js | 6 ++++-- .../_expected/server/index.svelte.js | 2 ++ .../snapshot/samples/function-prop-no-getter/index.svelte | 4 +++- 5 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 .changeset/pretty-ties-help.md diff --git a/.changeset/pretty-ties-help.md b/.changeset/pretty-ties-help.md new file mode 100644 index 0000000000..f2408416d4 --- /dev/null +++ b/.changeset/pretty-ties-help.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: skip generating $.proxy() calls for unary and binary expressions 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 b2928105e1..7b7076a69c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -525,6 +525,8 @@ export function should_proxy_or_freeze(node) { node.type === 'Literal' || node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression' || + node.type === 'UnaryExpression' || + node.type === 'BinaryExpression' || (node.type === 'Identifier' && node.name === 'undefined') ) { return false; 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 8fb2fbcf65..b53ee2523d 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 @@ -9,16 +9,18 @@ export default function Function_prop_no_getter($$anchor, $$props) { let count = $.source(0); function onmouseup() { - $.set(count, $.proxy($.get(count) + 2)); + $.set(count, $.get(count) + 2); } + const plusOne = (num) => num + 1; /* Init */ var fragment = $.comment($$anchor); var node = $.child_frag(fragment); Button(node, { - onmousedown: () => $.set(count, $.proxy($.get(count) + 1)), + onmousedown: () => $.set(count, $.get(count) + 1), onmouseup, + onmouseenter: () => $.set(count, $.proxy(plusOne($.get(count)))), children: ($$anchor, $$slotProps) => { /* Init */ var node_1 = $.space($$anchor); 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 ffd6a820bc..f678bb6ad6 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 @@ -11,6 +11,7 @@ export default function Function_prop_no_getter($$payload, $$props) { count += 2; } + const plusOne = (num) => num + 1; const anchor = $.create_anchor($$payload); $$payload.out += `${anchor}`; @@ -18,6 +19,7 @@ export default function Function_prop_no_getter($$payload, $$props) { Button($$payload, { onmousedown: () => count += 1, onmouseup, + onmouseenter: () => count = plusOne(count), children: ($$payload, $$slotProps) => { $$payload.out += `clicks: ${$.escape(count)}`; } diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/index.svelte b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/index.svelte index 1ca945b8f5..53bd9ae7ca 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/index.svelte +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/index.svelte @@ -4,8 +4,10 @@ function onmouseup() { count += 2; } + + const plusOne = (num) => num + 1; - From d56223b1629e353af864e1c40aed4eef0d5646fc Mon Sep 17 00:00:00 2001 From: navorite Date: Tue, 2 Jan 2024 13:30:22 +0200 Subject: [PATCH 04/10] fix: improve script `lang` attribute detection (#10046) closes #10038 --- .changeset/hip-balloons-begin.md | 5 + .../src/compiler/phases/1-parse/index.js | 11 +- .../comment-before-script/input.svelte | 4 + .../samples/comment-before-script/output.json | 132 ++++++++++++++++++ 4 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 .changeset/hip-balloons-begin.md create mode 100644 packages/svelte/tests/parser-modern/samples/comment-before-script/input.svelte create mode 100644 packages/svelte/tests/parser-modern/samples/comment-before-script/output.json diff --git a/.changeset/hip-balloons-begin.md b/.changeset/hip-balloons-begin.md new file mode 100644 index 0000000000..413b254065 --- /dev/null +++ b/.changeset/hip-balloons-begin.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve script `lang` attribute detection diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 6b7642c251..e99e91d052 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -11,7 +11,7 @@ import read_options from './read/options.js'; const regex_position_indicator = / \(\d+:\d+\)$/; const regex_lang_attribute = - /|]*|(?:[^=>'"/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)\s+)*)lang=(["'])?([^"' >]+)\1[^>]*>/; + /|]*|(?:[^=>'"/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)\s+)*)lang=(["'])?([^"' >]+)\1[^>]*>/g; export class Parser { /** @@ -49,7 +49,14 @@ export class Parser { this.template = template.trimRight(); - this.ts = regex_lang_attribute.exec(template)?.[2] === 'ts'; + let match_lang; + + do match_lang = regex_lang_attribute.exec(template); + while (match_lang && match_lang[0][1] !== 's'); // ensure it starts with ' + diff --git a/packages/svelte/tests/parser-modern/samples/comment-before-script/output.json b/packages/svelte/tests/parser-modern/samples/comment-before-script/output.json new file mode 100644 index 0000000000..d52a3752b4 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/comment-before-script/output.json @@ -0,0 +1,132 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 27, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Comment", + "start": 0, + "end": 27, + "data": "should not error out", + "ignores": [] + }, + { + "type": "Text", + "start": 27, + "end": 28, + "raw": "\n", + "data": "\n" + } + ], + "transparent": false + }, + "options": null, + "instance": { + "type": "Script", + "start": 28, + "end": 76, + "context": "default", + "content": { + "type": "Program", + "start": 46, + "end": 67, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 4, + "column": 0 + } + }, + "body": [ + { + "type": "VariableDeclaration", + "start": 48, + "end": 66, + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 19 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 52, + "end": 65, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "id": { + "type": "Identifier", + "start": 52, + "end": 18, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "name": "count", + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 57, + "end": 65, + "loc": { + "start": { + "line": 3, + "column": 10 + }, + "end": { + "line": 3, + "column": 18 + } + }, + "typeAnnotation": { + "type": "TSNumberKeyword", + "start": 59, + "end": 65, + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 18 + } + } + } + } + }, + "init": null + } + ], + "kind": "let" + } + ], + "sourceType": "module" + } + } +} From abc126630ac1e3ab482a732cd956045da58dd6e6 Mon Sep 17 00:00:00 2001 From: navorite Date: Tue, 2 Jan 2024 14:54:30 +0200 Subject: [PATCH 05/10] fix: add types for popover attributes and events (#10041) closes #10036, this also moves the HTMLDetailsElement toggle event to its interface as it was conflicting with HTMLElement popover toggle event. --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/orange-dingos-poke.md | 5 +++++ packages/svelte/elements.d.ts | 19 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 .changeset/orange-dingos-poke.md diff --git a/.changeset/orange-dingos-poke.md b/.changeset/orange-dingos-poke.md new file mode 100644 index 0000000000..3e9e6b8ade --- /dev/null +++ b/.changeset/orange-dingos-poke.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: add types for popover attributes and events diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 09df6ddbc8..b71e8e6728 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -59,6 +59,7 @@ export type WheelEventHandler = EventHandler = EventHandler; export type TransitionEventHandler = EventHandler; export type MessageEventHandler = EventHandler; +export type ToggleEventHandler = EventHandler; // // DOM Attributes @@ -136,10 +137,13 @@ export interface DOMAttributes { onerror?: EventHandler | undefined | null; // also a Media Event onerrorcapture?: EventHandler | undefined | null; // also a Media Event - // Detail Events - 'on:toggle'?: EventHandler | undefined | null; - ontoggle?: EventHandler | undefined | null; - ontogglecapture?: EventHandler | undefined | null; + // Popover Events + 'on:beforetoggle'?: ToggleEventHandler | undefined | null; + onbeforetoggle?: ToggleEventHandler | undefined | null; + onbeforetogglecapture?: ToggleEventHandler | undefined | null; + 'on:toggle'?: ToggleEventHandler | undefined | null; + ontoggle?: ToggleEventHandler | undefined | null; + ontogglecapture?: ToggleEventHandler | undefined | null; // Keyboard Events 'on:keydown'?: KeyboardEventHandler | undefined | null; @@ -727,6 +731,7 @@ export interface HTMLAttributes extends AriaAttributes, D title?: string | undefined | null; translate?: 'yes' | 'no' | '' | undefined | null; inert?: boolean | undefined | null; + popover?: 'auto' | 'manual' | '' | undefined | null; // Unknown radiogroup?: string | undefined | null; // , @@ -873,6 +878,8 @@ export interface HTMLButtonAttributes extends HTMLAttributes name?: string | undefined | null; type?: 'submit' | 'reset' | 'button' | undefined | null; value?: string | string[] | number | undefined | null; + popovertarget?: string | undefined | null; + popovertargetaction?: 'toggle' | 'show' | 'hide' | undefined | null; } export interface HTMLCanvasAttributes extends HTMLAttributes { @@ -897,6 +904,10 @@ export interface HTMLDetailsAttributes extends HTMLAttributes | undefined | null; + ontoggle?: EventHandler | undefined | null; + ontogglecapture?: EventHandler | undefined | null; } export interface HTMLDelAttributes extends HTMLAttributes { From 98a72f50684ff18af5595f0eec6b91770626adb4 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 2 Jan 2024 14:18:59 +0100 Subject: [PATCH 06/10] chore: check in types (#9863) To ensure that changes to code/types doesn't result in unwanted changes in type generation, or that bumps to dts-buddy don't cause unwanted regressions, we're checking in the generated types. Types should be committed as-is (don't format it with prettier!). CI is enhanced to check that git sees no changed files after generating the types, which would mean types have changed. --- .github/workflows/ci.yml | 9 +- .github/workflows/release.yml | 6 +- CONTRIBUTING.md | 6 +- packages/svelte/.gitignore | 3 +- packages/svelte/package.json | 5 +- .../scripts/{build.js => generate-types.js} | 0 packages/svelte/types/index.d.ts | 2570 +++++++++++++++++ pnpm-lock.yaml | 10 +- 8 files changed, 2598 insertions(+), 11 deletions(-) rename packages/svelte/scripts/{build.js => generate-types.js} (100%) create mode 100644 packages/svelte/types/index.d.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08820f0ed3..7c3969cd54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,4 +50,11 @@ jobs: with: node-version: 18 cache: pnpm - - run: 'pnpm i && pnpm check && pnpm lint' + - name: install + run: pnpm install --frozen-lockfile + - name: type check + run: pnpm check + - name: lint + run: pnpm lint + - name: build and check generated types + run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24b1229b42..7810870670 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,11 @@ jobs: node-version: 18.x cache: pnpm - - run: pnpm install --frozen-lockfile + - name: Install + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); } - name: Create Release Pull Request or Publish to npm id: changesets diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 167a62646e..613551574f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,6 +133,10 @@ To typecheck the codebase, run `pnpm check` inside `packages/svelte`. To typeche - `snake_case` for internal variable names and methods. - `camelCase` for public variable names and methods. +### Generating types + +Types are auto-generated from the source, but the result is checked in to ensure no accidental changes slip through. Run `pnpm generate:types` to regenerate the types. + ### Sending your pull request Please make sure the following is done when submitting a pull request: @@ -141,7 +145,7 @@ Please make sure the following is done when submitting a pull request: 1. Make sure your code lints (`pnpm lint`). 1. Make sure your tests pass (`pnpm test`). -All pull requests should be opened against the `main` branch. Make sure the PR does only one thing, otherwise please split it. +All pull requests should be opened against the `main` branch. Make sure the PR does only one thing, otherwise please split it. If this change should contribute to a version bump, run `npx changeset` at the root of the repository after a code change and select the appropriate packages. #### Breaking changes diff --git a/packages/svelte/.gitignore b/packages/svelte/.gitignore index faa8892951..e4925570e5 100644 --- a/packages/svelte/.gitignore +++ b/packages/svelte/.gitignore @@ -1,4 +1,5 @@ -/types +/types/*.map +/types/compiler /compiler.cjs /action.d.ts diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 50fb4f09c8..3cde08871a 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -90,11 +90,12 @@ "templating" ], "scripts": { - "build": "rollup -c && node scripts/build.js && node scripts/check-treeshakeability.js", + "build": "rollup -c && pnpm generate:types && node scripts/check-treeshakeability.js", "dev": "rollup -cw", "check": "tsc && cd ./tests/types && tsc", "check:watch": "tsc --watch", "generate:version": "node ./scripts/generate-version.js", + "generate:types": "node ./scripts/generate-types.js", "prepublishOnly": "pnpm build" }, "devDependencies": { @@ -106,7 +107,7 @@ "@rollup/plugin-virtual": "^3.0.2", "@types/aria-query": "^5.0.3", "@types/estree": "^1.0.5", - "dts-buddy": "^0.4.0", + "dts-buddy": "^0.4.3", "esbuild": "^0.19.2", "rollup": "^4.1.5", "source-map": "^0.7.4", diff --git a/packages/svelte/scripts/build.js b/packages/svelte/scripts/generate-types.js similarity index 100% rename from packages/svelte/scripts/build.js rename to packages/svelte/scripts/generate-types.js diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts new file mode 100644 index 0000000000..4db8209706 --- /dev/null +++ b/packages/svelte/types/index.d.ts @@ -0,0 +1,2570 @@ +declare module 'svelte' { + // This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are). + + /** + * @deprecated Svelte components were classes in Svelte 4. In Svelte 5, thy are not anymore. + * Use `mount` or `createRoot` instead to instantiate components. + * See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes) + * for more info. + */ + export interface ComponentConstructorOptions< + Props extends Record = Record + > { + target: Element | Document | ShadowRoot; + anchor?: Element; + props?: Props; + context?: Map; + hydrate?: boolean; + intro?: boolean; + $$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 & + (Props extends { children?: any } + ? {} + : Slots extends { default: any } + ? { children?: Snippet } + : {}); + + /** + * Can be used to create strongly typed Svelte components. + * + * #### Example: + * + * You have component library on npm called `component-library`, from which + * you export a component called `MyComponent`. For Svelte+TypeScript users, + * you want to provide typings. Therefore you create a `index.d.ts`: + * ```ts + * import { SvelteComponent } from "svelte"; + * export class MyComponent extends SvelteComponent<{foo: string}> {} + * ``` + * Typing this makes it possible for IDEs like VS Code with the Svelte extension + * to provide intellisense and to use the component like this in a Svelte file + * with TypeScript: + * ```svelte + * + * + * ``` + * + * This was the base class for Svelte components in Svelte 4. Svelte 5+ components + * are completely different under the hood. You should only use this type for typing, + * not actually instantiate components with `new` - use `mount` or `createRoot` instead. + * See [breaking changes](https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes) + * for more info. + */ + export class SvelteComponent< + Props extends Record = any, + Events extends Record = any, + Slots extends Record = any + > { + [prop: string]: any; + /** + * @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>); + /** + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + * */ + $$prop_def: PropsWithChildren; + /** + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + * + * */ + $$events_def: Events; + /** + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + * + * */ + $$slot_def: Slots; + + /** + * @deprecated This method only exists when using one of the legacy compatibility helpers, which + * is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes + * for more info. + */ + $destroy(): void; + + /** + * @deprecated This method only exists when using one of the legacy compatibility helpers, which + * is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes + * for more info. + */ + $on>( + type: K, + callback: (e: Events[K]) => void + ): () => void; + + /** + * @deprecated This method only exists when using one of the legacy compatibility helpers, which + * is a stop-gap solution. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes + * for more info. + */ + $set(props: Partial): void; + } + + /** + * @deprecated Use `SvelteComponent` instead. See TODO for more information. + */ + export class SvelteComponentTyped< + Props extends Record = any, + Events extends Record = any, + Slots extends Record = any + > extends SvelteComponent {} + + /** + * Convenience type to get the events the given component expects. Example: + * ```html + * + * + * + * ``` + */ + export type ComponentEvents = Comp extends SvelteComponent< + any, + infer Events + > + ? Events + : never; + + /** + * Convenience type to get the props the given component expects. Example: + * ```html + * + * ``` + */ + export type ComponentProps = Comp extends SvelteComponent + ? Props + : never; + + /** + * Convenience type to get the type of a Svelte component. Useful for example in combination with + * dynamic components using ``. + * + * Example: + * ```html + * + * + * + * + * ``` + */ + export type ComponentType = (new ( + options: ComponentConstructorOptions< + Comp extends SvelteComponent ? Props : Record + > + ) => Comp) & { + /** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ + element?: typeof HTMLElement; + }; + + const SnippetReturn: unique symbol; + + /** + * The type of a `#snippet` block. You can use it to (for example) express that your component expects a snippet of a certain type: + * ```ts + * let { banner } = $props<{ banner: Snippet<{ text: string }> }>(); + * ``` + * You can only call a snippet through the `{@render ...}` tag. + */ + export interface Snippet { + (arg: T): typeof SnippetReturn & { + _: 'functions passed to {@render ...} tags must use the `Snippet` type imported from "svelte"'; + }; + } + + interface DispatchOptions { + cancelable?: boolean; + } + + export interface EventDispatcher> { + // Implementation notes: + // - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode + // - | null | undefined is added for convenience, as they are equivalent for the custom event constructor (both result in a null detail) + ( + ...args: null extends EventMap[Type] + ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions] + : undefined extends EventMap[Type] + ? [type: Type, parameter?: EventMap[Type] | null | undefined, options?: DispatchOptions] + : [type: Type, parameter: EventMap[Type], options?: DispatchOptions] + ): boolean; + } + /** + * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. + * It must be called during the component's initialisation (but doesn't need to live *inside* the component; + * it can be called from an external module). + * + * If a function is returned _synchronously_ from `onMount`, it will be called when the component is unmounted. + * + * `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api). + * + * https://svelte.dev/docs/svelte#onmount + * */ + export function onMount(fn: () => NotFunction | Promise> | (() => any)): void; + /** + * Retrieves the context that belongs to the closest parent component with the specified `key`. + * Must be called during component initialisation. + * + * https://svelte.dev/docs/svelte#getcontext + * */ + export function getContext(key: any): T; + /** + * Associates an arbitrary `context` object with the current component and the specified `key` + * and returns that object. The context is then available to children of the component + * (including slotted content) with `getContext`. + * + * Like lifecycle functions, this must be called during component initialisation. + * + * https://svelte.dev/docs/svelte#setcontext + * */ + export function setContext(key: any, context: T): T; + /** + * Checks whether a given `key` has been set in the context of a parent component. + * Must be called during component initialisation. + * + * https://svelte.dev/docs/svelte#hascontext + * */ + export function hasContext(key: any): boolean; + /** + * Retrieves the whole context map that belongs to the closest parent component. + * Must be called during component initialisation. Useful, for example, if you + * programmatically create a component and want to pass the existing context to it. + * + * https://svelte.dev/docs/svelte#getallcontexts + * */ + export function getAllContexts = Map>(): T; + /** + * Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname). + * Event dispatchers are functions that can take two arguments: `name` and `detail`. + * + * Component events created with `createEventDispatcher` create a + * [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). + * These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture). + * The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) + * property and can contain any type of data. + * + * The event dispatcher can be typed to narrow the allowed event names and the type of the `detail` argument: + * ```ts + * const dispatch = createEventDispatcher<{ + * loaded: never; // does not take a detail argument + * change: string; // takes a detail argument of type string, which is required + * optional: number | null; // takes an optional detail argument of type number + * }>(); + * ``` + * + * https://svelte.dev/docs/svelte#createeventdispatcher + * */ + export function createEventDispatcher = any>(): EventDispatcher; + /** + * Schedules a callback to run immediately before the component is updated after any state change. + * + * The first time the callback runs will be before the initial `onMount`. + * + * In runes mode use `$effect.pre` instead. + * + * https://svelte.dev/docs/svelte#beforeupdate + * @deprecated Use `$effect.pre` instead — see https://svelte-5-preview.vercel.app/docs/deprecations#beforeupdate-and-afterupdate + * */ + export function beforeUpdate(fn: () => void): void; + /** + * Schedules a callback to run immediately after the component has been updated. + * + * The first time the callback runs will be after the initial `onMount`. + * + * In runes mode use `$effect` instead. + * + * https://svelte.dev/docs/svelte#afterupdate + * @deprecated Use `$effect` instead — see https://svelte-5-preview.vercel.app/docs/deprecations#beforeupdate-and-afterupdate + * */ + export function afterUpdate(fn: () => void): void; + /** + * Anything except a function + */ + type NotFunction = T extends Function ? never : T; + /** + * 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. + * + * If you don't need to interact with the component after mounting, use `mount` instead to save some bytes. + * + * */ + export function createRoot, Exports extends Record | undefined, Events extends Record>(component: { + new (options: ComponentConstructorOptions | undefined; + })>): SvelteComponent; + }, options: { + target: Node; + props?: Props | undefined; + events?: Events | undefined; + context?: Map | undefined; + intro?: boolean | undefined; + recover?: false | undefined; + }): Exports & { + $destroy: () => void; + $set: (props: Partial) => void; + }; + /** + * 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 `createRoot` instead. + * + * */ + export function mount, Exports extends Record | undefined, Events extends Record>(component: { + new (options: ComponentConstructorOptions | undefined; + })>): SvelteComponent; + }, options: { + target: Node; + props?: Props | undefined; + events?: Events | undefined; + context?: Map | undefined; + intro?: boolean | undefined; + recover?: false | undefined; + }): [Exports, () => void]; + /** + * Synchronously flushes any pending state changes and those that result from it. + * */ + export function flushSync(fn?: (() => void) | undefined): void; + /** + * Returns a promise that resolves once any pending state changes have been applied. + * */ + export function tick(): Promise; + /** + * Use `untrack` to prevent something from being treated as an `$effect`/`$derived` dependency. + * + * https://svelte-5-preview.vercel.app/docs/functions#untrack + * */ + export function untrack(fn: () => T): T; + /** + * Schedules a callback to run immediately before the component is unmounted. + * + * Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the + * only one that runs inside a server-side component. + * + * https://svelte.dev/docs/svelte#ondestroy + * */ + export function onDestroy(fn: () => any): void; + export function unstate(value: T): T; +} + +declare module 'svelte/action' { + /** + * Actions can return an object containing the two properties defined in this interface. Both are optional. + * - update: An action can have a parameter. This method will be called whenever that parameter changes, + * immediately after Svelte has applied updates to the markup. `ActionReturn` and `ActionReturn` both + * mean that the action accepts no parameters. + * - destroy: Method that is called after the element is unmounted + * + * Additionally, you can specify which additional attributes and events the action enables on the applied element. + * This applies to TypeScript typings only and has no effect at runtime. + * + * Example usage: + * ```ts + * interface Attributes { + * newprop?: string; + * 'on:event': (e: CustomEvent) => void; + * } + * + * export function myAction(node: HTMLElement, parameter: Parameter): ActionReturn { + * // ... + * return { + * update: (updatedParameter) => {...}, + * destroy: () => {...} + * }; + * } + * ``` + * + * Docs: https://svelte.dev/docs/svelte-action + */ + export interface ActionReturn< + Parameter = undefined, + Attributes extends Record = Record + > { + update?: (parameter: Parameter) => void; + destroy?: () => void; + /** + * ### DO NOT USE THIS + * This exists solely for type-checking and has no effect at runtime. + * Set this through the `Attributes` generic instead. + */ + $$_attributes?: Attributes; + } + + /** + * Actions are functions that are called when an element is created. + * You can use this interface to type such actions. + * The following example defines an action that only works on `
` elements + * and optionally accepts a parameter which it has a default value for: + * ```ts + * export const myAction: Action = (node, param = { someProperty: true }) => { + * // ... + * } + * ``` + * `Action` and `Action` both signal that the action accepts no parameters. + * + * You can return an object with methods `update` and `destroy` from the function and type which additional attributes and events it has. + * See interface `ActionReturn` for more details. + * + * Docs: https://svelte.dev/docs/svelte-action + */ + export interface Action< + Element = HTMLElement, + Parameter = undefined, + Attributes extends Record = Record + > { + ( + ...args: undefined extends Parameter + ? [node: Node, parameter?: Parameter] + : [node: Node, parameter: Parameter] + ): void | ActionReturn; + } + + // Implementation notes: + // - undefined extends X instead of X extends undefined makes this work better with both strict and nonstrict mode +} + +declare module 'svelte/animate' { + // todo: same as Transition, should it be shared? + export interface AnimationConfig { + delay?: number; + duration?: number; + easing?: (t: number) => number; + css?: (t: number, u: number) => string; + tick?: (t: number, u: number) => void; + } + + export interface FlipParams { + delay?: number; + duration?: number | ((len: number) => number); + easing?: (t: number) => number; + } + /** + * The flip function calculates the start and end position of an element and animates between them, translating the x and y values. + * `flip` stands for [First, Last, Invert, Play](https://aerotwist.com/blog/flip-your-animations/). + * + * https://svelte.dev/docs/svelte-animate#flip + * */ + export function flip(node: Element, { from, to }: { + from: DOMRect; + to: DOMRect; + }, params?: FlipParams): AnimationConfig; +} + +declare module 'svelte/compiler' { + import type { AssignmentExpression, ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, ArrayExpression, MemberExpression, ObjectExpression, Pattern, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, FunctionExpression, Node, Program } from 'estree'; + import type { Location } from 'locate-character'; + import type { SourceMap } from 'magic-string'; + import type { Context } from 'zimmerframe'; + /** + * `compile` converts your `.svelte` source code into a JavaScript module that exports a component + * + * https://svelte.dev/docs/svelte-compiler#svelte-compile + * @param source The component source code + * @param options The compiler options + * */ + export function compile(source: string, options: CompileOptions): CompileResult; + /** + * `compileModule` takes your JavaScript source code containing runes, and turns it into a JavaScript module. + * + * https://svelte.dev/docs/svelte-compiler#svelte-compile + * @param source The component source code + * */ + export function compileModule(source: string, options: ModuleCompileOptions): CompileResult; + /** + * The parse function parses a component, returning only its abstract syntax tree. + * + * The `modern` option (`false` by default in Svelte 5) makes the parser return a modern AST instead of the legacy AST. + * `modern` will become `true` by default in Svelte 6, and the option will be removed in Svelte 7. + * + * https://svelte.dev/docs/svelte-compiler#svelte-parse + * */ + export function parse(source: string, options?: { + filename?: string | undefined; + modern?: boolean | undefined; + } | undefined): SvelteNode | LegacySvelteNode; + /** + * @deprecated Replace this with `import { walk } from 'estree-walker'` + * */ + function walk(): never; + /** The return value of `compile` from `svelte/compiler` */ + interface CompileResult { + /** The compiled JavaScript */ + js: { + /** The generated code */ + code: string; + /** A source map */ + map: SourceMap; + }; + /** The compiled CSS */ + css: null | { + /** The generated code */ + code: string; + /** A source map */ + map: SourceMap; + }; + /** + * An array of warning objects that were generated during compilation. Each warning has several properties: + * - `code` is a string identifying the category of warning + * - `message` describes the issue in human-readable terms + * - `start` and `end`, if the warning relates to a specific location, are objects with `line`, `column` and `character` properties + */ + warnings: Warning[]; + /** + * Metadata about the compiled component + */ + metadata: { + /** + * Whether the file was compiled in runes mode, either because of an explicit option or inferred from usage. + * For `compileModule`, this is always `true` + */ + runes: boolean; + }; + } + + interface Warning { + start?: Location; + end?: Location; + // TODO there was pos: number in Svelte 4 - do we want to add it back? + code: string; + message: string; + filename?: string; + } + + interface CompileError_1 extends Error { + code: string; + filename?: string; + position?: [number, number]; + start?: Location; + end?: Location; + } + + type CssHashGetter = (args: { + name: string; + filename: string | undefined; + css: string; + hash: (input: string) => string; + }) => string; + + interface CompileOptions extends ModuleCompileOptions { + /** + * Sets the name of the resulting JavaScript class (though the compiler will rename it if it would otherwise conflict with other variables in scope). + * If unspecified, will be inferred from `filename` + */ + name?: string; + /** + * If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component. + * + * @default false + */ + customElement?: boolean; + /** + * If `true`, getters and setters will be created for the component's props. If `false`, they will only be created for readonly exported values (i.e. those declared with `const`, `class` and `function`). If compiling with `customElement: true` this option defaults to `true`. + * + * @default false + */ + accessors?: boolean; + /** + * The namespace of the element; e.g., `"html"`, `"svg"`, `"foreign"`. + * + * @default 'html' + */ + namespace?: Namespace; + /** + * If `true`, tells the compiler that you promise not to mutate any objects. + * This allows it to be less conservative about checking whether values have changed. + * + * @default false + */ + immutable?: boolean; + /** + * - `'injected'`: styles will be included in the JavaScript class and injected at runtime for the components actually rendered. + * - `'external'`: the CSS will be returned in the `css` field of the compilation result. Most Svelte bundler plugins will set this to `'external'` and use the CSS that is statically generated for better performance, as it will result in smaller JavaScript bundles and the output can be served as cacheable `.css` files. + * This is always `'injected'` when compiling with `customElement` mode. + */ + css?: 'injected' | 'external'; + /** + * A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. + * It defaults to returning `svelte-${hash(css)}`. + * + * @default undefined + */ + cssHash?: CssHashGetter; + /** + * If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out. + * + * @default false + */ + preserveComments?: boolean; + /** + * If `true`, whitespace inside and between elements is kept as you typed it, rather than removed or collapsed to a single space where possible. + * + * @default false + */ + preserveWhitespace?: boolean; + /** + * Set to `true` to force the compiler into runes mode, even if there are no indications of runes usage. + * Set to `false` to force the compiler into ignoring runes, even if there are indications of runes usage. + * Set to `undefined` (the default) to infer runes mode from the component code. + * Is always `true` for JS/TS modules compiled with Svelte. + * Will be `true` by default in Svelte 6. + * @default undefined + */ + runes?: boolean | undefined; + /** + * If `true`, exposes the Svelte major version on the global `window` object in the browser. + * + * @default true + */ + discloseVersion?: boolean; + /** + * @deprecated Use these only as a temporary solution before migrating your code + */ + legacy?: { + /** + * Applies a transformation so that the default export of Svelte files can still be instantiated the same way as in Svelte 4 — + * as a class when compiling for the browser (as though using `createClassComponent(MyComponent, {...})` from `svelte/legacy`) + * or as an object with a `.render(...)` method when compiling for the server + * @default false + */ + componentApi?: boolean; + }; + /** + * An initial sourcemap that will be merged into the final output sourcemap. + * This is usually the preprocessor sourcemap. + * + * @default null + */ + sourcemap?: object | string; + /** + * Used for your JavaScript sourcemap. + * + * @default null + */ + outputFilename?: string; + /** + * Used for your CSS sourcemap. + * + * @default null + */ + cssOutputFilename?: string; + + // Other Svelte 4 compiler options: + // enableSourcemap?: EnableSourcemap; // TODO bring back? https://github.com/sveltejs/svelte/pull/6835 + // legacy?: boolean; // TODO compiler error noting the new purpose? + } + + interface ModuleCompileOptions { + /** + * If `true`, causes extra code to be added that will perform runtime checks and provide debugging information during development. + * + * @default false + */ + dev?: boolean; + /** + * If `"client"`, Svelte emits code designed to run in the browser. + * If `"server"`, Svelte emits code suitable for server-side rendering. + * If `false`, nothing is generated. Useful for tooling that is only interested in warnings. + * + * @default 'client' + */ + generate?: 'client' | 'server' | false; + /** + * Used for debugging hints and sourcemaps. Your bundler plugin will set it automatically. + */ + filename?: string; + } + + type DeclarationKind = + | 'var' + | 'let' + | 'const' + | 'function' + | 'import' + | 'param' + | 'rest_param' + | 'synthetic'; + + interface Binding { + node: Identifier; + /** + * - `normal`: A variable that is not in any way special + * - `prop`: A normal prop (possibly mutated) + * - `rest_prop`: A rest prop + * - `state`: A state variable + * - `derived`: A derived variable + * - `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' + | 'prop' + | 'rest_prop' + | 'state' + | 'frozen_state' + | 'derived' + | 'each' + | 'store_sub' + | 'legacy_reactive' + | 'legacy_reactive_import'; + declaration_kind: DeclarationKind; + /** + * What the value was initialized with. + * For destructured props such as `let { foo = 'bar' } = $props()` this is `'bar'` and not `$props()` + */ + initial: null | Expression | FunctionDeclaration | ClassDeclaration | ImportDeclaration; + is_called: boolean; + references: { node: Identifier; path: SvelteNode[] }[]; + mutated: boolean; + reassigned: boolean; + scope: Scope; + /** For `legacy_reactive`: its reactive dependencies */ + legacy_dependencies: Binding[]; + /** Legacy props: the `class` in `{ export klass as class}` */ + prop_alias: string | null; + /** If this is set, all references should use this expression instead of the identifier name */ + expression: Expression | null; + /** If this is set, all mutations should use this expression */ + mutation: ((assignment: AssignmentExpression, context: Context) => Expression) | null; + } + interface BaseNode_1 { + type: string; + start: number; + end: number; + } + + interface BaseElement_1 extends BaseNode_1 { + name: string; + attributes: Array; + children: Array; + } + + interface LegacyAction extends BaseNode_1 { + type: 'Action'; + /** The 'x' in `use:x` */ + name: string; + /** The 'y' in `use:x={y}` */ + expression: null | Expression; + } + + interface LegacyAnimation extends BaseNode_1 { + type: 'Animation'; + /** The 'x' in `animate:x` */ + name: string; + /** The y in `animate:x={y}` */ + expression: null | Expression; + } + + interface LegacyBinding extends BaseNode_1 { + type: 'Binding'; + /** The 'x' in `bind:x` */ + name: string; + /** The y in `bind:x={y}` */ + expression: Identifier | MemberExpression; + } + + interface LegacyBody extends BaseElement_1 { + type: 'Body'; + name: 'svelte:body'; + } + + interface LegacyAttribute extends BaseNode_1 { + type: 'Attribute'; + name: string; + value: true | Array; + } + + interface LegacyAttributeShorthand extends BaseNode_1 { + type: 'AttributeShorthand'; + expression: Expression; + } + + interface LegacyLet extends BaseNode_1 { + type: 'Let'; + /** The 'x' in `let:x` */ + name: string; + /** The 'y' in `let:x={y}` */ + expression: null | Identifier | ArrayExpression | ObjectExpression; + } + + interface LegacyCatchBlock extends BaseNode_1 { + type: 'CatchBlock'; + children: LegacySvelteNode[]; + skip: boolean; + } + + interface LegacyClass extends BaseNode_1 { + type: 'Class'; + /** The 'x' in `class:x` */ + name: 'class'; + /** The 'y' in `class:x={y}`, or the `x` in `class:x` */ + expression: Expression; + } + + interface LegacyDocument extends BaseElement_1 { + type: 'Document'; + } + + interface LegacyElement { + type: 'Element'; + } + + interface LegacyEventHandler extends BaseNode_1 { + type: 'EventHandler'; + /** The 'x' in `on:x` */ + name: string; + /** The 'y' in `on:x={y}` */ + expression: null | Expression; + modifiers: string[]; + } + + interface LegacyHead extends BaseElement_1 { + type: 'Head'; + } + + interface LegacyInlineComponent extends BaseElement_1 { + type: 'InlineComponent'; + /** Set if this is a `` */ + expression?: Expression; + } + + interface LegacyMustacheTag extends BaseNode_1 { + type: 'MustacheTag'; + expression: Expression; + } + + interface LegacyOptions { + type: 'Options'; + name: 'svelte:options'; + attributes: Array; + } + + interface LegacyPendingBlock extends BaseNode_1 { + type: 'PendingBlock'; + children: LegacySvelteNode[]; + skip: boolean; + } + + interface LegacyRawMustacheTag extends BaseNode_1 { + type: 'RawMustacheTag'; + expression: Expression; + } + + interface LegacySpread extends BaseNode_1 { + type: 'Spread'; + expression: Expression; + } + + interface LegacySlot extends BaseElement_1 { + type: 'Slot'; + } + + interface LegacySlotTemplate extends BaseElement_1 { + type: 'SlotTemplate'; + } + + interface LegacyThenBlock extends BaseNode_1 { + type: 'ThenBlock'; + children: LegacySvelteNode[]; + skip: boolean; + } + + interface LegacyTitle extends BaseElement_1 { + type: 'Title'; + name: 'title'; + } + + interface LegacyConstTag extends BaseNode_1 { + type: 'ConstTag'; + expression: AssignmentExpression; + } + + interface LegacyTransition extends BaseNode_1 { + type: 'Transition'; + /** The 'x' in `transition:x` */ + name: string; + /** The 'y' in `transition:x={y}` */ + expression: null | Expression; + modifiers: Array<'local' | 'global'>; + /** True if this is a `transition:` or `in:` directive */ + intro: boolean; + /** True if this is a `transition:` or `out:` directive */ + outro: boolean; + } + + interface LegacyWindow extends BaseElement_1 { + type: 'Window'; + } + + type LegacyDirective = + | LegacyAnimation + | LegacyBinding + | LegacyClass + | LegacyLet + | LegacyEventHandler + | StyleDirective + | LegacyTransition + | LegacyAction; + + type LegacyAttributeLike = LegacyAttribute | LegacySpread | LegacyDirective; + + type LegacyElementLike = + | LegacyBody + | LegacyCatchBlock + | LegacyDocument + | LegacyElement + | LegacyHead + | LegacyInlineComponent + | LegacyMustacheTag + | LegacyOptions + | LegacyPendingBlock + | LegacyRawMustacheTag + | LegacySlot + | LegacySlotTemplate + | LegacyThenBlock + | LegacyTitle + | LegacyWindow; + + type LegacySvelteNode = + | LegacyConstTag + | LegacyElementLike + | LegacyAttributeLike + | LegacyAttributeShorthand + | Text; + /** + * The preprocess function provides convenient hooks for arbitrarily transforming component source code. + * For example, it can be used to convert a 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 index 3716981b37..87ab7c02eb 100644 --- a/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json +++ b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/output.json @@ -2,7 +2,7 @@ "css": { "type": "Style", "start": 0, - "end": 586, + "end": 806, "attributes": [], "children": [ { @@ -601,32 +601,292 @@ }, "start": 530, "end": 577 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 580, + "end": 601, + "children": [ + { + "type": "Selector", + "start": 580, + "end": 601, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 580, + "end": 582 + }, + { + "type": "PseudoClassSelector", + "name": "nth-of-type", + "args": { + "type": "SelectorList", + "start": 595, + "end": 600, + "children": [ + { + "type": "Selector", + "start": 595, + "end": 600, + "children": [ + { + "type": "Nth", + "value": "10n+1", + "start": 595, + "end": 600 + } + ] + } + ] + }, + "start": 582, + "end": 601 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 601, + "end": 633, + "children": [ + { + "type": "Declaration", + "start": 611, + "end": 626, + "property": "background", + "value": "red" + } + ] + }, + "start": 580, + "end": 633 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 636, + "end": 657, + "children": [ + { + "type": "Selector", + "start": 636, + "end": 657, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 636, + "end": 638 + }, + { + "type": "PseudoClassSelector", + "name": "nth-of-type", + "args": { + "type": "SelectorList", + "start": 651, + "end": 656, + "children": [ + { + "type": "Selector", + "start": 651, + "end": 656, + "children": [ + { + "type": "Nth", + "value": "-2n+3", + "start": 651, + "end": 656 + } + ] + } + ] + }, + "start": 638, + "end": 657 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 657, + "end": 689, + "children": [ + { + "type": "Declaration", + "start": 667, + "end": 682, + "property": "background", + "value": "red" + } + ] + }, + "start": 636, + "end": 689 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 692, + "end": 711, + "children": [ + { + "type": "Selector", + "start": 692, + "end": 711, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 692, + "end": 694 + }, + { + "type": "PseudoClassSelector", + "name": "nth-of-type", + "args": { + "type": "SelectorList", + "start": 707, + "end": 710, + "children": [ + { + "type": "Selector", + "start": 707, + "end": 710, + "children": [ + { + "type": "Nth", + "value": "+12", + "start": 707, + "end": 710 + } + ] + } + ] + }, + "start": 694, + "end": 711 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 711, + "end": 743, + "children": [ + { + "type": "Declaration", + "start": 721, + "end": 736, + "property": "background", + "value": "red" + } + ] + }, + "start": 692, + "end": 743 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 746, + "end": 765, + "children": [ + { + "type": "Selector", + "start": 746, + "end": 765, + "children": [ + { + "type": "TypeSelector", + "name": "h1", + "start": 746, + "end": 748 + }, + { + "type": "PseudoClassSelector", + "name": "nth-of-type", + "args": { + "type": "SelectorList", + "start": 761, + "end": 764, + "children": [ + { + "type": "Selector", + "start": 761, + "end": 764, + "children": [ + { + "type": "Nth", + "value": "+3n", + "start": 761, + "end": 764 + } + ] + } + ] + }, + "start": 748, + "end": 765 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 765, + "end": 797, + "children": [ + { + "type": "Declaration", + "start": 775, + "end": 790, + "property": "background", + "value": "red" + } + ] + }, + "start": 746, + "end": 797 } ], "content": { "start": 7, - "end": 578, - "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 h1:nth-child(\n n\n ) {\n background: red;\n }\n h1:global(nav) {\n background: red;\n }\n" + "end": 798, + "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 h1:nth-child(\n n\n ) {\n background: red;\n }\n h1:global(nav) {\n background: red;\n }\n\t\th1:nth-of-type(10n+1){\n background: red;\n }\n\t\th1:nth-of-type(-2n+3){\n background: red;\n }\n\t\th1:nth-of-type(+12){\n background: red;\n }\n\t\th1:nth-of-type(+3n){\n background: red;\n }\n" } }, "js": [], - "start": 588, - "end": 600, + "start": 808, + "end": 820, "type": "Root", "fragment": { "type": "Fragment", "nodes": [ { "type": "Text", - "start": 586, - "end": 588, + "start": 806, + "end": 808, "raw": "\n\n", "data": "\n\n" }, { "type": "RegularElement", - "start": 588, - "end": 600, + "start": 808, + "end": 820, "name": "h1", "attributes": [], "fragment": { @@ -634,8 +894,8 @@ "nodes": [ { "type": "Text", - "start": 592, - "end": 595, + "start": 812, + "end": 815, "raw": "Foo", "data": "Foo" } From e46a71e8a326ae0b3045cf6cde8be5fc7e988e4e Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 2 Jan 2024 16:25:58 +0100 Subject: [PATCH 09/10] fix: handle pseudo class elements with content (#10055) closes #9398 (the other things in that issue are already addressed) closes #10019 --- .changeset/nasty-lions-double.md | 5 + .changeset/sweet-pens-sniff.md | 5 + .../src/compiler/phases/1-parse/read/style.js | 6 + .../compiler/phases/2-analyze/css/Selector.js | 4 +- .../samples/css-pseudo-classes/input.svelte | 18 ++ .../samples/css-pseudo-classes/output.json | 246 ++++++++++++++++++ .../errors.json | 4 +- .../input.svelte | 3 + 8 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 .changeset/nasty-lions-double.md create mode 100644 .changeset/sweet-pens-sniff.md create mode 100644 packages/svelte/tests/parser-modern/samples/css-pseudo-classes/input.svelte create mode 100644 packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json diff --git a/.changeset/nasty-lions-double.md b/.changeset/nasty-lions-double.md new file mode 100644 index 0000000000..404e08168d --- /dev/null +++ b/.changeset/nasty-lions-double.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: improve pseudo class parsing diff --git a/.changeset/sweet-pens-sniff.md b/.changeset/sweet-pens-sniff.md new file mode 100644 index 0000000000..165c1e05f7 --- /dev/null +++ b/.changeset/sweet-pens-sniff.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow pseudo classes after `:global(..)` 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 4d1777638d..600c10f33a 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -227,6 +227,12 @@ function read_selector(parser, inside_pseudo_class = false) { start, end: parser.index }); + // We read the inner selectors of a pseudo element to ensure it parses correctly, + // but we don't do anything with the result. + if (parser.eat('(')) { + read_selector_list(parser, true); + parser.eat(')', true); + } } else if (parser.eat(':')) { const name = read_identifier(parser); 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 2a8e4eff40..b6796ff961 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js @@ -184,7 +184,9 @@ export default class Selector { selector.name === 'global' && block.selectors.length !== 1 && (i === block.selectors.length - 1 || - block.selectors.slice(i + 1).some((s) => s.type !== 'PseudoElementSelector')) + block.selectors + .slice(i + 1) + .some((s) => s.type !== 'PseudoElementSelector' && s.type !== 'PseudoClassSelector')) ) { error(selector, 'invalid-css-global-selector-list'); } diff --git a/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/input.svelte b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/input.svelte new file mode 100644 index 0000000000..6602f9c044 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/input.svelte @@ -0,0 +1,18 @@ + diff --git a/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json new file mode 100644 index 0000000000..f502060e13 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/css-pseudo-classes/output.json @@ -0,0 +1,246 @@ +{ + "css": { + "type": "Style", + "start": 0, + "end": 313, + "attributes": [], + "children": [ + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 60, + "end": 86, + "children": [ + { + "type": "Selector", + "start": 60, + "end": 86, + "children": [ + { + "type": "PseudoElementSelector", + "name": "view-transition-old", + "start": 60, + "end": 81 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 88, + "end": 109, + "children": [ + { + "type": "Declaration", + "start": 92, + "end": 102, + "property": "color", + "value": "red" + } + ] + }, + "start": 60, + "end": 109 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 111, + "end": 146, + "children": [ + { + "type": "Selector", + "start": 111, + "end": 146, + "children": [ + { + "type": "PseudoClassSelector", + "name": "global", + "args": { + "type": "SelectorList", + "start": 119, + "end": 145, + "children": [ + { + "type": "Selector", + "start": 119, + "end": 145, + "children": [ + { + "type": "PseudoElementSelector", + "name": "view-transition-old", + "start": 119, + "end": 140 + } + ] + } + ] + }, + "start": 111, + "end": 146 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 148, + "end": 169, + "children": [ + { + "type": "Declaration", + "start": 152, + "end": 162, + "property": "color", + "value": "red" + } + ] + }, + "start": 111, + "end": 169 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 171, + "end": 199, + "children": [ + { + "type": "Selector", + "start": 171, + "end": 199, + "children": [ + { + "type": "PseudoElementSelector", + "name": "highlight", + "start": 171, + "end": 182 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 200, + "end": 218, + "children": [ + { + "type": "Declaration", + "start": 204, + "end": 214, + "property": "color", + "value": "red" + } + ] + }, + "start": 171, + "end": 218 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 220, + "end": 245, + "children": [ + { + "type": "Selector", + "start": 220, + "end": 245, + "children": [ + { + "type": "TypeSelector", + "name": "custom-element", + "start": 220, + "end": 234 + }, + { + "type": "PseudoElementSelector", + "name": "part", + "start": 234, + "end": 240 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 246, + "end": 264, + "children": [ + { + "type": "Declaration", + "start": 250, + "end": 260, + "property": "color", + "value": "red" + } + ] + }, + "start": 220, + "end": 264 + }, + { + "type": "Rule", + "prelude": { + "type": "SelectorList", + "start": 266, + "end": 285, + "children": [ + { + "type": "Selector", + "start": 266, + "end": 285, + "children": [ + { + "type": "PseudoElementSelector", + "name": "slotted", + "start": 266, + "end": 275 + } + ] + } + ] + }, + "block": { + "type": "Block", + "start": 286, + "end": 304, + "children": [ + { + "type": "Declaration", + "start": 290, + "end": 300, + "property": "color", + "value": "red" + } + ] + }, + "start": 266, + "end": 304 + } + ], + "content": { + "start": 7, + "end": 305, + "styles": "\n /* test that all these are parsed correctly */\n\t::view-transition-old(x-y) {\n\t\tcolor: red;\n }\n\t:global(::view-transition-old(x-y)) {\n\t\tcolor: red;\n }\n\t::highlight(rainbow-color-1) {\n\t\tcolor: red;\n\t}\n\tcustom-element::part(foo) {\n\t\tcolor: red;\n\t}\n\t::slotted(.content) {\n\t\tcolor: red;\n\t}\n" + } + }, + "js": [], + "start": null, + "end": null, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [], + "transparent": false + }, + "options": null +} 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 7ba32a53fb..53e24d8d81 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 @@ -3,11 +3,11 @@ "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, + "line": 5, "column": 6 }, "end": { - "line": 2, + "line": 5, "column": 19 } } diff --git a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/input.svelte b/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/input.svelte index 75bd7b66e1..2c8e96a484 100644 --- a/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/input.svelte +++ b/packages/svelte/tests/validator/samples/css-invalid-global-placement-2/input.svelte @@ -1,4 +1,7 @@