diff --git a/.changeset/curvy-ties-shout.md b/.changeset/curvy-ties-shout.md new file mode 100644 index 0000000000..ee8bc567e3 --- /dev/null +++ b/.changeset/curvy-ties-shout.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: deeply unstate objects passed to inspect 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/.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/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/.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/.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/.changeset/three-suits-grin.md b/.changeset/three-suits-grin.md new file mode 100644 index 0000000000..7fa0154e5f --- /dev/null +++ b/.changeset/three-suits-grin.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: parse `:nth-of-type(xn+y)` correctly diff --git a/.changeset/unlucky-trees-lick.md b/.changeset/unlucky-trees-lick.md new file mode 100644 index 0000000000..6e6f68b7c6 --- /dev/null +++ b/.changeset/unlucky-trees-lick.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure if block is executed in correct order 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/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: 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/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 { 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/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 '|\|\|)\s*/; const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/; const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/; -const REGEX_NTH_OF = /^\s*(even|odd|(-?[0-9]?n?(\s*\+\s*[0-9]+)?))(\s*(?=[,)])|\s+of\s+)/; +const REGEX_NTH_OF = + /^\s*(even|odd|\+?(\d+|\d*n(\s*[+-]\s*\d+)?)|-\d*n(\s*\+\s*\d+))(\s*(?=[,)])|\s+of\s+)/; const REGEX_WHITESPACE_OR_COLON = /[\s:]/; const REGEX_BRACE_OR_SEMICOLON = /[{;]/; const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/; @@ -226,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); @@ -277,6 +284,14 @@ function read_selector(parser, inside_pseudo_class = false) { value, flags }); + } else if (inside_pseudo_class && parser.match_regex(REGEX_NTH_OF)) { + // nth of matcher must come before combinator matcher to prevent collision else the '+' in '+2n-1' would be parsed as a combinator + children.push({ + type: 'Nth', + value: /** @type {string} */ (parser.read(REGEX_NTH_OF)), + start, + end: parser.index + }); } else if (parser.match_regex(REGEX_COMBINATOR_WHITESPACE)) { parser.allow_whitespace(); const start = parser.index; @@ -294,13 +309,6 @@ function read_selector(parser, inside_pseudo_class = false) { start, end: parser.index }); - } else if (inside_pseudo_class && 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('|')) { 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/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 9b75c73cbb..c08c135183 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -526,6 +526,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/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 15e25d278c..946a6c3196 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -64,7 +64,7 @@ let current_dependencies = null; let current_dependencies_index = 0; /** @type {null | import('./types.js').Signal[]} */ let current_untracked_writes = null; -/** @type {null | import('./types.js').Signal} */ +/** @type {null | import('./types.js').SignalDebug} */ let last_inspected_signal = null; /** If `true`, `get`ting the signal should not register it as a dependency */ export let current_untracking = false; @@ -81,7 +81,7 @@ let captured_signals = new Set(); /** @type {Function | null} */ let inspect_fn = null; -/** @type {Array} */ +/** @type {Array} */ let inspect_captured_signals = []; // Handle rendering tree blocks and anchors @@ -127,7 +127,6 @@ export function batch_inspect(target, prop, receiver) { } finally { is_batching_effect = previously_batching_effect; if (last_inspected_signal !== null) { - // @ts-expect-error for (const fn of last_inspected_signal.inspect) fn(); last_inspected_signal = null; } @@ -349,7 +348,21 @@ function execute_signal_fn(signal) { if (current_dependencies !== null) { let i; - remove_consumer(signal, current_dependencies_index, false); + if (dependencies !== null) { + const dep_length = dependencies.length; + // If we have more than 16 elements in the array then use a Set for faster performance + // TODO: evaluate if we should always just use a Set or not here? + const current_dependencies_set = dep_length > 16 ? new Set(current_dependencies) : null; + for (i = current_dependencies_index; i < dep_length; i++) { + const dependency = dependencies[i]; + if ( + (current_dependencies_set !== null && !current_dependencies_set.has(dependency)) || + !current_dependencies.includes(dependency) + ) { + remove_consumer(signal, dependency, false); + } + } + } if (dependencies !== null && current_dependencies_index > 0) { dependencies.length = current_dependencies_index + current_dependencies.length; @@ -365,16 +378,17 @@ function execute_signal_fn(signal) { if (!current_skip_consumer) { for (i = current_dependencies_index; i < dependencies.length; i++) { const dependency = dependencies[i]; + const consumers = dependency.c; - if (dependency.c === null) { + if (consumers === null) { dependency.c = [signal]; - } else { - dependency.c.push(signal); + } else if (consumers[consumers.length - 1] !== signal) { + consumers.push(signal); } } } } else if (dependencies !== null && current_dependencies_index < dependencies.length) { - remove_consumer(signal, current_dependencies_index, false); + remove_consumers(signal, current_dependencies_index, false); dependencies.length = current_dependencies_index; } return res; @@ -390,6 +404,40 @@ function execute_signal_fn(signal) { } } +/** + * @template V + * @param {import('./types.js').ComputationSignal} signal + * @param {import('./types.js').Signal} dependency + * @param {boolean} remove_unowned + * @returns {void} + */ +function remove_consumer(signal, dependency, remove_unowned) { + const consumers = dependency.c; + let consumers_length = 0; + if (consumers !== null) { + consumers_length = consumers.length - 1; + const index = consumers.indexOf(signal); + if (index !== -1) { + if (consumers_length === 0) { + dependency.c = null; + } else { + // Swap with last element and then remove. + consumers[index] = consumers[consumers_length]; + consumers.pop(); + } + } + } + if (remove_unowned && consumers_length === 0 && (dependency.f & UNOWNED) !== 0) { + // If the signal is unowned then we need to make sure to change it to dirty. + set_signal_status(dependency, DIRTY); + remove_consumers( + /** @type {import('./types.js').ComputationSignal} **/ (dependency), + 0, + true + ); + } +} + /** * @template V * @param {import('./types.js').ComputationSignal} signal @@ -397,36 +445,13 @@ function execute_signal_fn(signal) { * @param {boolean} remove_unowned * @returns {void} */ -function remove_consumer(signal, start_index, remove_unowned) { +function remove_consumers(signal, start_index, remove_unowned) { const dependencies = signal.d; if (dependencies !== null) { let i; for (i = start_index; i < dependencies.length; i++) { const dependency = dependencies[i]; - const consumers = dependency.c; - let consumers_length = 0; - if (consumers !== null) { - consumers_length = consumers.length - 1; - const index = consumers.indexOf(signal); - if (index !== -1) { - if (consumers_length === 0) { - dependency.c = null; - } else { - // Swap with last element and then remove. - consumers[index] = consumers[consumers_length]; - consumers.pop(); - } - } - } - if (remove_unowned && consumers_length === 0 && (dependency.f & UNOWNED) !== 0) { - // If the signal is unowned then we need to make sure to change it to dirty. - set_signal_status(dependency, DIRTY); - remove_consumer( - /** @type {import('./types.js').ComputationSignal} **/ (dependency), - 0, - true - ); - } + remove_consumer(signal, dependency, remove_unowned); } } } @@ -446,7 +471,7 @@ function destroy_references(signal) { if ((reference.f & IS_EFFECT) !== 0) { destroy_signal(reference); } else { - remove_consumer(reference, 0, true); + remove_consumers(reference, 0, true); reference.d = null; } } @@ -711,8 +736,7 @@ function update_derived(signal, force_schedule) { // @ts-expect-error if (DEV && signal.inspect && force_schedule) { - // @ts-expect-error - for (const fn of signal.inspect) fn(); + for (const fn of /** @type {import('./types.js').SignalDebug} */ (signal).inspect) fn(); } } } @@ -815,8 +839,7 @@ export function unsubscribe_on_destroy(stores) { export function get(signal) { // @ts-expect-error if (DEV && signal.inspect && inspect_fn) { - // @ts-expect-error - signal.inspect.add(inspect_fn); + /** @type {import('./types.js').SignalDebug} */ (signal).inspect.add(inspect_fn); // @ts-expect-error inspect_captured_signals.push(signal); } @@ -841,10 +864,16 @@ export function get(signal) { !(unowned && current_effect !== null) ) { current_dependencies_index++; - } else if (current_dependencies === null) { - current_dependencies = [signal]; - } else if (signal !== current_dependencies[current_dependencies.length - 1]) { - current_dependencies.push(signal); + } else if ( + dependencies === null || + current_dependencies_index === 0 || + dependencies[current_dependencies_index - 1] !== signal + ) { + if (current_dependencies === null) { + current_dependencies = [signal]; + } else if (signal !== current_dependencies[current_dependencies.length - 1]) { + current_dependencies.push(signal); + } } if ( current_untracked_writes !== null && @@ -1079,10 +1108,9 @@ export function set_signal_value(signal, value) { // @ts-expect-error if (DEV && signal.inspect) { if (is_batching_effect) { - last_inspected_signal = signal; + last_inspected_signal = /** @type {import('./types.js').SignalDebug} */ (signal); } else { - // @ts-expect-error - for (const fn of signal.inspect) fn(); + for (const fn of /** @type {import('./types.js').SignalDebug} */ (signal).inspect) fn(); } } } @@ -1098,7 +1126,7 @@ export function destroy_signal(signal) { const destroy = signal.y; const flags = signal.f; destroy_references(signal); - remove_consumer(signal, 0, true); + remove_consumers(signal, 0, true); signal.i = signal.r = signal.y = @@ -1804,6 +1832,37 @@ function deep_read(value, visited = new Set()) { } } +/** + * Like `unstate`, but recursively traverses into normal arrays/objects to find potential states in them. + * @param {any} value + * @param {Map} visited + * @returns {any} + */ +function deep_unstate(value, visited = new Map()) { + if (typeof value === 'object' && value !== null && !visited.has(value)) { + const unstated = unstate(value); + if (unstated !== value) { + visited.set(value, unstated); + return unstated; + } + + let contains_unstated = false; + /** @type {any} */ + const nested_unstated = Array.isArray(value) ? [] : {}; + for (let key in value) { + const result = deep_unstate(value[key], visited); + nested_unstated[key] = result; + if (result !== value[key]) { + contains_unstated = true; + } + } + + visited.set(value, contains_unstated ? nested_unstated : value); + } + + return visited.get(value) ?? value; +} + // TODO remove in a few versions, before 5.0 at the latest let warned_inspect_changed = false; @@ -1817,7 +1876,7 @@ export function inspect(get_value, inspect = console.log) { pre_effect(() => { const fn = () => { - const value = get_value().map(unstate); + const value = get_value().map((v) => deep_unstate(v)); if (value.length === 2 && typeof value[1] === 'function' && !warned_inspect_changed) { // eslint-disable-next-line no-console console.warn( diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 3a73f797e3..af21817c69 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -108,6 +108,8 @@ export type ComputationSignal = { export type Signal = SourceSignal | ComputationSignal; +export type SignalDebug = SourceSignalDebug & Signal; + export type EffectSignal = ComputationSignal void)>; export type MaybeSignal = T | Signal; diff --git a/packages/svelte/tests/parser-modern/samples/comment-before-script/input.svelte b/packages/svelte/tests/parser-modern/samples/comment-before-script/input.svelte new file mode 100644 index 0000000000..5086ed08bb --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/comment-before-script/input.svelte @@ -0,0 +1,4 @@ + + 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" + } + } +} 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 index 5668a7799a..ce3813f3ad 100644 --- a/packages/svelte/tests/parser-modern/samples/css-nth-syntax/input.svelte +++ b/packages/svelte/tests/parser-modern/samples/css-nth-syntax/input.svelte @@ -28,6 +28,18 @@ } h1:global(nav) { background: red; + } + h1:nth-of-type(10n+1){ + background: red; + } + h1:nth-of-type(-2n+3){ + background: red; + } + h1:nth-of-type(+12){ + background: red; + } + h1:nth-of-type(+3n){ + background: red; } 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" } 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/runtime-runes/samples/block-dependency-sequence/Component.svelte b/packages/svelte/tests/runtime-runes/samples/block-dependency-sequence/Component.svelte new file mode 100644 index 0000000000..ef8f82b73a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/block-dependency-sequence/Component.svelte @@ -0,0 +1,9 @@ + + +
+ {#if item} + {item.length} + {/if} +
diff --git a/packages/svelte/tests/runtime-runes/samples/block-dependency-sequence/_config.js b/packages/svelte/tests/runtime-runes/samples/block-dependency-sequence/_config.js new file mode 100644 index 0000000000..0d09ed54ec --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/block-dependency-sequence/_config.js @@ -0,0 +1,26 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ assert, target, component }) { + const [b1, b2] = target.querySelectorAll('button'); + assert.htmlEqual( + target.innerHTML, + '
5
5
3
+ diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-nested-state/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-nested-state/_config.js new file mode 100644 index 0000000000..32c9e98296 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-nested-state/_config.js @@ -0,0 +1,40 @@ +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 }) { + const [b1] = target.querySelectorAll('button'); + b1.click(); + await Promise.resolve(); + + assert.deepEqual(log, [ + 'init', + { x: { count: 0 } }, + [{ count: 0 }], + 'update', + { x: { count: 1 } }, + [{ count: 1 }] + ]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-nested-state/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-nested-state/main.svelte new file mode 100644 index 0000000000..68a171b80d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-nested-state/main.svelte @@ -0,0 +1,7 @@ + + + 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; - 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 @@