diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2d3e45049c1..0653b08b7640 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,13 +101,13 @@ Test samples are kept in `/test/xxx/samples` folder. 1. To run test, run `pnpm test`. 1. To run a particular test suite, use `pnpm test `, for example: - ```bash + ```sh pnpm test validator ``` 1. To filter tests _within_ a test suite, use `pnpm test -t `, for example: - ```bash + ```sh pnpm test validator -t a11y-alt-text ``` diff --git a/documentation/docs/01-introduction/02-getting-started.md b/documentation/docs/01-introduction/02-getting-started.md index c7351729ff17..e97a46ad34a8 100644 --- a/documentation/docs/01-introduction/02-getting-started.md +++ b/documentation/docs/01-introduction/02-getting-started.md @@ -4,7 +4,7 @@ title: Getting started We recommend using [SvelteKit](../kit), which lets you [build almost anything](../kit/project-types). It's the official application framework from the Svelte team and powered by [Vite](https://vite.dev/). Create a new project with: -```bash +```sh npx sv create myapp cd myapp npm install diff --git a/documentation/docs/07-misc/02-testing.md b/documentation/docs/07-misc/02-testing.md index db99b7077022..bcec4db0a39b 100644 --- a/documentation/docs/07-misc/02-testing.md +++ b/documentation/docs/07-misc/02-testing.md @@ -10,7 +10,7 @@ Unit tests allow you to test small isolated parts of your code. Integration test To setup Vitest manually, first install it: -```bash +```sh npm install -D vitest ``` @@ -166,7 +166,7 @@ It is possible to test your components in isolation using Vitest. To get started, install jsdom (a library that shims DOM APIs): -```bash +```sh npm install -D jsdom ``` diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index 20f57770d122..957a9f67c7b0 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -364,6 +364,12 @@ The $ name is reserved, and cannot be used for variables and imports The $ prefix is reserved, and cannot be used for variables and imports ``` +### duplicate_class_field + +``` +`%name%` has already been declared +``` + ### each_item_invalid_assignment ``` diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md index 2af9021a6a7e..01003f30c57a 100644 --- a/documentation/docs/98-reference/.generated/compile-warnings.md +++ b/documentation/docs/98-reference/.generated/compile-warnings.md @@ -683,7 +683,7 @@ Some templating languages (including Svelte) will 'fix' HTML by turning ` extends AriaAttributes, DO height?: number | string | undefined | null; id?: string | undefined | null; lang?: string | undefined | null; + part?: string | undefined | null; max?: number | string | undefined | null; media?: string | undefined | null; // On the `textPath` element diff --git a/packages/svelte/messages/compile-errors/script.md b/packages/svelte/messages/compile-errors/script.md index 2b0c5eafdf86..5c1080acedfe 100644 --- a/packages/svelte/messages/compile-errors/script.md +++ b/packages/svelte/messages/compile-errors/script.md @@ -30,6 +30,10 @@ > The $ prefix is reserved, and cannot be used for variables and imports +## duplicate_class_field + +> `%name%` has already been declared + ## each_item_invalid_assignment > Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`) diff --git a/packages/svelte/messages/compile-warnings/template.md b/packages/svelte/messages/compile-warnings/template.md index d61a61d95051..8af5aa2b98b0 100644 --- a/packages/svelte/messages/compile-warnings/template.md +++ b/packages/svelte/messages/compile-warnings/template.md @@ -71,7 +71,7 @@ Some templating languages (including Svelte) will 'fix' HTML by turning `} */ const state_fields = new Map(); + /** @type {Map>} */ + const fields = new Map(); + context.state.analysis.classes.set(node, state_fields); /** @type {MethodDefinition | null} */ @@ -54,6 +57,14 @@ export function ClassBody(node, context) { e.state_field_duplicate(node, name); } + const _key = (key.type === 'PrivateIdentifier' ? '#' : '') + name; + const field = fields.get(_key); + + // if there's already a method or assigned field, error + if (field && !(field.length === 1 && field[0] === 'prop')) { + e.duplicate_class_field(node, _key); + } + state_fields.set(name, { node, type: rune, @@ -67,10 +78,48 @@ export function ClassBody(node, context) { for (const child of node.body) { if (child.type === 'PropertyDefinition' && !child.computed && !child.static) { handle(child, child.key, child.value); + const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key); + const field = fields.get(key); + if (!field) { + fields.set(key, [child.value ? 'assigned_prop' : 'prop']); + continue; + } + e.duplicate_class_field(child, key); } - if (child.type === 'MethodDefinition' && child.kind === 'constructor') { - constructor = child; + if (child.type === 'MethodDefinition') { + if (child.kind === 'constructor') { + constructor = child; + } else if (!child.computed) { + const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key); + const field = fields.get(key); + if (!field) { + fields.set(key, [child.kind]); + continue; + } + if ( + field.includes(child.kind) || + field.includes('prop') || + field.includes('assigned_prop') + ) { + e.duplicate_class_field(child, key); + } + if (child.kind === 'get') { + if (field.length === 1 && field[0] === 'set') { + field.push('get'); + continue; + } + } else if (child.kind === 'set') { + if (field.length === 1 && field[0] === 'get') { + field.push('set'); + continue; + } + } else { + field.push(child.kind); + continue; + } + e.duplicate_class_field(child, key); + } } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js index cced326f9baa..4dfdfe5af1a1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -93,7 +93,10 @@ export function Identifier(node, context) { context.state.expression.references.add(binding); context.state.expression.has_state ||= binding.kind !== 'static' && - !binding.is_function() && + (binding.kind === 'prop' || + binding.kind === 'bindable_prop' || + binding.kind === 'rest_prop' || + !binding.is_function()) && !context.state.scope.evaluate(node).is_known; } diff --git a/packages/svelte/src/compiler/validate-options.js b/packages/svelte/src/compiler/validate-options.js index ed83375d2236..2b727ad09335 100644 --- a/packages/svelte/src/compiler/validate-options.js +++ b/packages/svelte/src/compiler/validate-options.js @@ -8,7 +8,7 @@ import * as w from './warnings.js'; * @typedef {(input: Input, keypath: string) => Required} Validator */ -const common = { +const common_options = { filename: string('(unknown)'), // default to process.cwd() where it exists to replicate svelte4 behavior (and make Deno work with this as well) @@ -48,110 +48,120 @@ const common = { }) }; -export const validate_module_options = - /** @type {Validator} */ ( - object({ - ...common - }) - ); +const component_options = { + accessors: deprecate(w.options_deprecated_accessors, boolean(false)), -export const validate_component_options = - /** @type {Validator} */ ( - object({ - ...common, + css: validator('external', (input) => { + if (input === true || input === false) { + throw_error( + 'The boolean options have been removed from the css option. Use "external" instead of false and "injected" instead of true' + ); + } + if (input === 'none') { + throw_error( + 'css: "none" is no longer a valid option. If this was crucial for you, please open an issue on GitHub with your use case.' + ); + } - accessors: deprecate(w.options_deprecated_accessors, boolean(false)), + if (input !== 'external' && input !== 'injected') { + throw_error(`css should be either "external" (default, recommended) or "injected"`); + } - css: validator('external', (input) => { - if (input === true || input === false) { - throw_error( - 'The boolean options have been removed from the css option. Use "external" instead of false and "injected" instead of true' - ); - } - if (input === 'none') { - throw_error( - 'css: "none" is no longer a valid option. If this was crucial for you, please open an issue on GitHub with your use case.' - ); - } + return input; + }), - if (input !== 'external' && input !== 'injected') { - throw_error(`css should be either "external" (default, recommended) or "injected"`); - } + cssHash: fun(({ css, hash }) => { + return `svelte-${hash(css)}`; + }), + + // TODO this is a sourcemap option, would be good to put under a sourcemap namespace + cssOutputFilename: string(undefined), + + customElement: boolean(false), + + discloseVersion: boolean(true), - return input; - }), + immutable: deprecate(w.options_deprecated_immutable, boolean(false)), - cssHash: fun(({ css, hash }) => { - return `svelte-${hash(css)}`; - }), + legacy: removed( + 'The legacy option has been removed. If you are using this because of legacy.componentApi, use compatibility.componentApi instead' + ), + + compatibility: object({ + componentApi: list([4, 5], 5) + }), + + loopGuardTimeout: warn_removed(w.options_removed_loop_guard_timeout), + + name: string(undefined), - // TODO this is a sourcemap option, would be good to put under a sourcemap namespace - cssOutputFilename: string(undefined), + namespace: list(['html', 'mathml', 'svg']), - customElement: boolean(false), + modernAst: boolean(false), - discloseVersion: boolean(true), + outputFilename: string(undefined), - immutable: deprecate(w.options_deprecated_immutable, boolean(false)), + preserveComments: boolean(false), - legacy: removed( - 'The legacy option has been removed. If you are using this because of legacy.componentApi, use compatibility.componentApi instead' - ), + fragments: list(['html', 'tree']), - compatibility: object({ - componentApi: list([4, 5], 5) - }), + preserveWhitespace: boolean(false), - loopGuardTimeout: warn_removed(w.options_removed_loop_guard_timeout), + runes: boolean(undefined), - name: string(undefined), + hmr: boolean(false), - namespace: list(['html', 'mathml', 'svg']), + sourcemap: validator(undefined, (input) => { + // Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map, + // so there's no good way to check type validity here + return input; + }), - modernAst: boolean(false), + enableSourcemap: warn_removed(w.options_removed_enable_sourcemap), - outputFilename: string(undefined), + hydratable: warn_removed(w.options_removed_hydratable), - preserveComments: boolean(false), + format: removed( + 'The format option has been removed in Svelte 4, the compiler only outputs ESM now. Remove "format" from your compiler options. ' + + 'If you did not set this yourself, bump the version of your bundler plugin (vite-plugin-svelte/rollup-plugin-svelte/svelte-loader)' + ), - fragments: list(['html', 'tree']), + tag: removed( + 'The tag option has been removed in Svelte 5. Use `` inside the component instead. ' + + 'If that does not solve your use case, please open an issue on GitHub with details.' + ), - preserveWhitespace: boolean(false), + sveltePath: removed( + 'The sveltePath option has been removed in Svelte 5. ' + + 'If this option was crucial for you, please open an issue on GitHub with your use case.' + ), - runes: boolean(undefined), + // These two were primarily created for svelte-preprocess (https://github.com/sveltejs/svelte/pull/6194), + // but with new TypeScript compilation modes strictly separating types it's not necessary anymore + errorMode: removed( + 'The errorMode option has been removed. If you are using this through svelte-preprocess with TypeScript, ' + + 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead' + ), - hmr: boolean(false), + varsReport: removed( + 'The vars option has been removed. If you are using this through svelte-preprocess with TypeScript, ' + + 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead' + ) +}; - sourcemap: validator(undefined, (input) => { - // Source maps can take on a variety of values, including string, JSON, map objects from magic-string and source-map, - // so there's no good way to check type validity here - return input; - }), +export const validate_module_options = + /** @type {Validator} */ ( + object({ + ...common_options, + ...Object.fromEntries(Object.keys(component_options).map((key) => [key, () => {}])) + }) + ); - enableSourcemap: warn_removed(w.options_removed_enable_sourcemap), - hydratable: warn_removed(w.options_removed_hydratable), - format: removed( - 'The format option has been removed in Svelte 4, the compiler only outputs ESM now. Remove "format" from your compiler options. ' + - 'If you did not set this yourself, bump the version of your bundler plugin (vite-plugin-svelte/rollup-plugin-svelte/svelte-loader)' - ), - tag: removed( - 'The tag option has been removed in Svelte 5. Use `` inside the component instead. ' + - 'If that does not solve your use case, please open an issue on GitHub with details.' - ), - sveltePath: removed( - 'The sveltePath option has been removed in Svelte 5. ' + - 'If this option was crucial for you, please open an issue on GitHub with your use case.' - ), - // These two were primarily created for svelte-preprocess (https://github.com/sveltejs/svelte/pull/6194), - // but with new TypeScript compilation modes strictly separating types it's not necessary anymore - errorMode: removed( - 'The errorMode option has been removed. If you are using this through svelte-preprocess with TypeScript, ' + - 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead' - ), - varsReport: removed( - 'The vars option has been removed. If you are using this through svelte-preprocess with TypeScript, ' + - 'use the https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax setting instead' - ) +export const validate_component_options = + /** @type {Validator} */ ( + object({ + ...common_options, + ...component_options }) ); diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 5d76fc3f2963..e83ba6fb3018 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.36.16'; +export const VERSION = '5.37.0'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js new file mode 100644 index 000000000000..6b281f04f0ee --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/_config.js @@ -0,0 +1,15 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + mode: ['client'], + + test({ assert, target }) { + const btn = target.querySelector('button'); + + assert.htmlEqual(target.innerHTML, ` Inner: 0 Inner: 0`); + btn?.click(); + flushSync(); + assert.htmlEqual(target.innerHTML, ` Inner: 1 Inner: 1`); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte new file mode 100644 index 000000000000..6bde0a15a87c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/inner.svelte @@ -0,0 +1,4 @@ + +Inner: {getter()} diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte new file mode 100644 index 000000000000..2cb2f67b82f7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/main.svelte @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte new file mode 100644 index 000000000000..525494ddfbf1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte new file mode 100644 index 000000000000..9498f432d8ce --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/props-default-value-function/wrapper2.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json b/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json index b7dd4c8ed457..94b5f191c2f7 100644 --- a/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json +++ b/packages/svelte/tests/validator/samples/class-state-constructor-9/errors.json @@ -1,14 +1,14 @@ [ { - "code": "state_field_invalid_assignment", - "message": "Cannot assign to a state field before its declaration", + "code": "duplicate_class_field", + "message": "`count` has already been declared", "start": { - "line": 2, - "column": 1 + "line": 5, + "column": 2 }, "end": { - "line": 2, - "column": 12 + "line": 5, + "column": 24 } } ] pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy