diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbdd1e420c..b2bcb08848 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: run: pnpm lint - name: build and check generated types if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail - 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); } + run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` and commit the changes after you have reviewed them"; git diff; exit 1); } Benchmarks: runs-on: ubuntu-latest timeout-minutes: 15 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a17f49bbeb..1daef0b89c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: 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); } + run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally with `cd packages/svelte && pnpm generate:types` 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/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md index f6f2585df2..3d515305fe 100644 --- a/documentation/docs/98-reference/.generated/compile-warnings.md +++ b/documentation/docs/98-reference/.generated/compile-warnings.md @@ -630,6 +630,32 @@ In some situations a selector may target an element that is not 'visible' to the Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...>` rather than `<%name% ... />` ``` +In HTML, there's [no such thing as a self-closing tag](https://jakearchibald.com/2023/against-self-closing-tags-in-html/). While this _looks_ like a self-contained element with some text next to it... + +```html +
+ some text! +
+``` + +...a spec-compliant HTML parser (such as a browser) will in fact parse it like this, with the text _inside_ the icon: + +```html +
+ some text! +
+``` + +Some templating languages (including Svelte) will 'fix' HTML by turning `` into ``. Others adhere to the spec. Both result in ambiguity and confusion when copy-pasting code between different contexts, and as such Svelte prompts you to resolve the ambiguity directly by having an explicit closing tag. + +To automate this, run the dedicated migration: + +```bash +npx sv migrate self-closing-tags +``` + +In a future version of Svelte, self-closing tags may be upgraded from a warning to an error. + ### event_directive_deprecated ``` diff --git a/documentation/docs/99-legacy/30-legacy-svelte-component.md b/documentation/docs/99-legacy/30-legacy-svelte-component.md index 5d385a9c02..3da2e3350e 100644 --- a/documentation/docs/99-legacy/30-legacy-svelte-component.md +++ b/documentation/docs/99-legacy/30-legacy-svelte-component.md @@ -2,7 +2,7 @@ title: --- -In runes mode, `` will re-render if the value of `MyComponent` changes. +In runes mode, `` will re-render if the value of `MyComponent` changes. See the [Svelte 5 migration guide](/docs/svelte/v5-migration-guide#Breaking-changes-in-runes-mode-svelte:component-is-no-longer-necessary) for an example. In legacy mode, it won't — we must use ``, which destroys and recreates the component instance when the value of its `this` expression changes: diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 09e21aec84..1e9c3451f3 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,59 @@ # svelte +## 5.15.0 + +### Minor Changes + +- feat: add "worker" exports condition to better support bundling for worker-based environments ([#14779](https://github.com/sveltejs/svelte/pull/14779)) + +## 5.14.6 + +### Patch Changes + +- fix: treeshake `$inspect.trace` code if unused in modules ([#14774](https://github.com/sveltejs/svelte/pull/14774)) + +- fix: Improve typescript DX for $inspect, $props, $bindable, and $host ([#14777](https://github.com/sveltejs/svelte/pull/14777)) + +## 5.14.5 + +### Patch Changes + +- fix: bump esrap dependency ([#14765](https://github.com/sveltejs/svelte/pull/14765)) + +- fix: ensure svg namespace for `` elements is correct ([#14756](https://github.com/sveltejs/svelte/pull/14756)) + +- fix: treeshake `$inspect.trace` code if unused ([#14770](https://github.com/sveltejs/svelte/pull/14770)) + +## 5.14.4 + +### Patch Changes + +- fix: remove implements from class declarations ([#14749](https://github.com/sveltejs/svelte/pull/14749)) + +- fix: remove unwanted properties from both replaced and unreplaced nodes ([#14744](https://github.com/sveltejs/svelte/pull/14744)) + +## 5.14.3 + +### Patch Changes + +- fix: bump esrap, prevent malformed AST ([#14742](https://github.com/sveltejs/svelte/pull/14742)) + +- fix: compare array contents for equality mismatch detections, not the arrays themselves ([#14738](https://github.com/sveltejs/svelte/pull/14738)) + +## 5.14.2 + +### Patch Changes + +- fix: correctly highlight first rerun of `$inspect.trace` ([#14734](https://github.com/sveltejs/svelte/pull/14734)) + +- chore: more loose parser improvements ([#14733](https://github.com/sveltejs/svelte/pull/14733)) + +## 5.14.1 + +### Patch Changes + +- fix: improve unowned derived performance ([#14724](https://github.com/sveltejs/svelte/pull/14724)) + ## 5.14.0 ### Minor Changes diff --git a/packages/svelte/messages/compile-warnings/template.md b/packages/svelte/messages/compile-warnings/template.md index 33e635bdb2..c1675e5995 100644 --- a/packages/svelte/messages/compile-warnings/template.md +++ b/packages/svelte/messages/compile-warnings/template.md @@ -34,6 +34,32 @@ > Self-closing HTML tags for non-void elements are ambiguous — use `<%name% ...>` rather than `<%name% ... />` +In HTML, there's [no such thing as a self-closing tag](https://jakearchibald.com/2023/against-self-closing-tags-in-html/). While this _looks_ like a self-contained element with some text next to it... + +```html +
+ some text! +
+``` + +...a spec-compliant HTML parser (such as a browser) will in fact parse it like this, with the text _inside_ the icon: + +```html +
+ some text! +
+``` + +Some templating languages (including Svelte) will 'fix' HTML by turning `` into ``. Others adhere to the spec. Both result in ambiguity and confusion when copy-pasting code between different contexts, and as such Svelte prompts you to resolve the ambiguity directly by having an explicit closing tag. + +To automate this, run the dedicated migration: + +```bash +npx sv migrate self-closing-tags +``` + +In a future version of Svelte, self-closing tags may be upgraded from a warning to an error. + ## event_directive_deprecated > Using `on:%name%` to listen to the %name% event is deprecated. Use the event attribute `on%name%` instead diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 27e78e681e..05169a7bc2 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.14.0", + "version": "5.15.0", "type": "module", "types": "./types/index.d.ts", "engines": { @@ -21,6 +21,7 @@ "exports": { ".": { "types": "./types/index.d.ts", + "worker": "./src/index-server.js", "browser": "./src/index-client.js", "default": "./src/index-server.js" }, @@ -56,11 +57,15 @@ "./internal/flags/legacy": { "default": "./src/internal/flags/legacy.js" }, + "./internal/flags/tracing": { + "default": "./src/internal/flags/tracing.js" + }, "./internal/server": { "default": "./src/internal/server/index.js" }, "./legacy": { "types": "./types/index.d.ts", + "worker": "./src/legacy/legacy-server.js", "browser": "./src/legacy/legacy-client.js", "default": "./src/legacy/legacy-server.js" }, @@ -70,6 +75,7 @@ }, "./reactivity": { "types": "./types/index.d.ts", + "worker": "./src/reactivity/index-server.js", "browser": "./src/reactivity/index-client.js", "default": "./src/reactivity/index-server.js" }, @@ -83,6 +89,7 @@ }, "./store": { "types": "./types/index.d.ts", + "worker": "./src/store/index-server.js", "browser": "./src/store/index-client.js", "default": "./src/store/index-server.js" }, @@ -147,7 +154,7 @@ "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "esm-env": "^1.2.1", - "esrap": "^1.2.3", + "esrap": "^1.3.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", diff --git a/packages/svelte/scripts/check-treeshakeability.js b/packages/svelte/scripts/check-treeshakeability.js index 1b50111b87..1501ee6954 100644 --- a/packages/svelte/scripts/check-treeshakeability.js +++ b/packages/svelte/scripts/check-treeshakeability.js @@ -58,6 +58,7 @@ for (const key in pkg.exports) { if (key === './internal') continue; if (key === './internal/disclose-version') continue; if (key === './internal/flags/legacy') continue; + if (key === './internal/flags/tracing') continue; for (const type of ['browser', 'default']) { if (!pkg.exports[key][type]) continue; @@ -91,6 +92,7 @@ const bundle = await bundle_code( hi +
a a @@ -134,6 +136,15 @@ if (!bundle.includes('component_context.l')) { console.error(`❌ Legacy code not treeshakeable`); } +if (!bundle.includes(`'CreatedAt'`)) { + // eslint-disable-next-line no-console + console.error(`✅ $inspect.trace code treeshakeable`); +} else { + failed = true; + // eslint-disable-next-line no-console + console.error(`❌ $inspect.trace code not treeshakeable`); +} + if (failed) { // eslint-disable-next-line no-console console.error(bundle); diff --git a/packages/svelte/scripts/generate-types.js b/packages/svelte/scripts/generate-types.js index 16bbf52a2e..d44afe8205 100644 --- a/packages/svelte/scripts/generate-types.js +++ b/packages/svelte/scripts/generate-types.js @@ -46,6 +46,8 @@ await createBundle({ } }); +fs.appendFileSync(`${dir}/types/index.d.ts`, '\n'); + const types = fs.readFileSync(`${dir}/types/index.d.ts`, 'utf-8'); const bad_links = [...types.matchAll(/\]\((\/[^)]+)\)/g)]; diff --git a/packages/svelte/scripts/process-messages/index.js b/packages/svelte/scripts/process-messages/index.js index bbe257498e..80619acfa7 100644 --- a/packages/svelte/scripts/process-messages/index.js +++ b/packages/svelte/scripts/process-messages/index.js @@ -389,7 +389,6 @@ function transform(name, dest) { ast.body.push(clone); } - // @ts-expect-error const module = esrap.print(ast); fs.writeFileSync( diff --git a/packages/svelte/src/ambient.d.ts b/packages/svelte/src/ambient.d.ts index 4e98eb82eb..9dbc61c7cb 100644 --- a/packages/svelte/src/ambient.d.ts +++ b/packages/svelte/src/ambient.d.ts @@ -338,6 +338,29 @@ declare namespace $effect { */ declare function $props(): any; +declare namespace $props { + // prevent intellisense from being unhelpful + /** @deprecated */ + export const apply: never; + /** @deprecated */ + // @ts-ignore + export const arguments: never; + /** @deprecated */ + export const bind: never; + /** @deprecated */ + export const call: never; + /** @deprecated */ + export const caller: never; + /** @deprecated */ + export const length: never; + /** @deprecated */ + export const name: never; + /** @deprecated */ + export const prototype: never; + /** @deprecated */ + export const toString: never; +} + /** * Declares a prop as bindable, meaning the parent component can use `bind:propName={value}` to bind to it. * @@ -349,6 +372,29 @@ declare function $props(): any; */ declare function $bindable(fallback?: T): T; +declare namespace $bindable { + // prevent intellisense from being unhelpful + /** @deprecated */ + export const apply: never; + /** @deprecated */ + // @ts-ignore + export const arguments: never; + /** @deprecated */ + export const bind: never; + /** @deprecated */ + export const call: never; + /** @deprecated */ + export const caller: never; + /** @deprecated */ + export const length: never; + /** @deprecated */ + export const name: never; + /** @deprecated */ + export const prototype: never; + /** @deprecated */ + export const toString: never; +} + /** * Inspects one or more values whenever they, or the properties they contain, change. Example: * @@ -388,6 +434,27 @@ declare namespace $inspect { * */ export function trace(name: string): void; + + // prevent intellisense from being unhelpful + /** @deprecated */ + export const apply: never; + /** @deprecated */ + // @ts-ignore + export const arguments: never; + /** @deprecated */ + export const bind: never; + /** @deprecated */ + export const call: never; + /** @deprecated */ + export const caller: never; + /** @deprecated */ + export const length: never; + /** @deprecated */ + export const name: never; + /** @deprecated */ + export const prototype: never; + /** @deprecated */ + export const toString: never; } /** @@ -410,3 +477,26 @@ declare namespace $inspect { * https://svelte.dev/docs/svelte/$host */ declare function $host(): El; + +declare namespace $host { + // prevent intellisense from being unhelpful + /** @deprecated */ + export const apply: never; + /** @deprecated */ + // @ts-ignore + export const arguments: never; + /** @deprecated */ + export const bind: never; + /** @deprecated */ + export const call: never; + /** @deprecated */ + export const caller: never; + /** @deprecated */ + export const length: never; + /** @deprecated */ + export const name: never; + /** @deprecated */ + export const prototype: never; + /** @deprecated */ + export const toString: never; +} diff --git a/packages/svelte/src/compiler/phases/1-parse/read/expression.js b/packages/svelte/src/compiler/phases/1-parse/read/expression.js index 021bd95a6e..907608b11b 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/expression.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/expression.js @@ -7,9 +7,10 @@ import { find_matching_bracket } from '../utils/bracket.js'; /** * @param {Parser} parser + * @param {string} [opening_token] * @returns {Expression} */ -export default function read_expression(parser) { +export default function read_expression(parser, opening_token) { try { const node = parse_expression_at(parser.template, parser.ts, parser.index); @@ -42,7 +43,7 @@ export default function read_expression(parser) { } catch (err) { if (parser.loose) { // Find the next } and treat it as the end of the expression - const end = find_matching_bracket(parser.template, parser.index, '{'); + const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{'); if (end) { const start = parser.index; parser.index = end; diff --git a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js index b968e95213..18c805128d 100644 --- a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js +++ b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js @@ -17,6 +17,16 @@ function remove_this_param(node, context) { /** @type {Visitors} */ const visitors = { + _(node, context) { + const n = context.next() ?? node; + + // TODO there may come a time when we decide to preserve type annotations. + // until that day comes, we just delete them so they don't confuse esrap + delete n.typeAnnotation; + delete n.typeParameters; + delete n.returnType; + delete n.accessibility; + }, Decorator(node) { e.typescript_invalid_feature(node, 'decorators (related TSC proposal is not stage 4 yet)'); }, @@ -78,23 +88,12 @@ const visitors = { TSNonNullExpression(node, context) { return context.visit(node.expression); }, - TSTypeAnnotation() { - // This isn't correct, strictly speaking, and could result in invalid ASTs (like an empty statement within function parameters), - // but esrap, our printing tool, just ignores these AST nodes at invalid positions, so it's fine - return b.empty; - }, TSInterfaceDeclaration() { return b.empty; }, TSTypeAliasDeclaration() { return b.empty; }, - TSTypeParameterDeclaration() { - return b.empty; - }, - TSTypeParameterInstantiation() { - return b.empty; - }, TSEnumDeclaration(node) { e.typescript_invalid_feature(node, 'enums'); }, @@ -116,6 +115,7 @@ const visitors = { if (node.declare) { return b.empty; } + delete node.implements; return context.next(); }, VariableDeclaration(node, context) { diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index c112cf6789..66946a8f8d 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -123,8 +123,11 @@ export default function element(parser) { } if (!regex_valid_element_name.test(name) && !regex_valid_component_name.test(name)) { - const bounds = { start: start + 1, end: start + 1 + name.length }; - e.tag_invalid_name(bounds); + // in the middle of typing -> allow in loose mode + if (!parser.loose || !name.endsWith('.')) { + const bounds = { start: start + 1, end: start + 1 + name.length }; + e.tag_invalid_name(bounds); + } } if (root_only_meta_tags.has(name)) { @@ -141,7 +144,7 @@ export default function element(parser) { const type = meta_tags.has(name) ? meta_tags.get(name) - : regex_valid_component_name.test(name) + : regex_valid_component_name.test(name) || (parser.loose && name.endsWith('.')) ? 'Component' : name === 'title' && parent_is_head(parser.stack) ? 'TitleElement' diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 423ada792c..7996d64ded 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -174,13 +174,30 @@ function open(parser) { if (parser.eat('(')) { parser.allow_whitespace(); - key = read_expression(parser); + key = read_expression(parser, '('); parser.allow_whitespace(); parser.eat(')', true); parser.allow_whitespace(); } - parser.eat('}', true); + const matches = parser.eat('}', true, false); + + if (!matches) { + // Parser may have read the `as` as part of the expression (e.g. in `{#each foo. as x}`) + if (parser.template.slice(parser.index - 4, parser.index) === ' as ') { + const prev_index = parser.index; + context = read_pattern(parser); + parser.eat('}', true); + expression = { + type: 'Identifier', + name: '', + start: expression.start, + end: prev_index - 4 + }; + } else { + parser.eat('}', true); // rerun to produce the parser error + } + } /** @type {AST.EachBlock} */ const block = parser.append({ @@ -246,7 +263,39 @@ function open(parser) { parser.fragments.push(block.pending); } - parser.eat('}', true); + const matches = parser.eat('}', true, false); + + // Parser may have read the `then/catch` as part of the expression (e.g. in `{#await foo. then x}`) + if (!matches) { + if (parser.template.slice(parser.index - 6, parser.index) === ' then ') { + const prev_index = parser.index; + block.value = read_pattern(parser); + parser.eat('}', true); + block.expression = { + type: 'Identifier', + name: '', + start: expression.start, + end: prev_index - 6 + }; + block.then = block.pending; + block.pending = null; + } else if (parser.template.slice(parser.index - 7, parser.index) === ' catch ') { + const prev_index = parser.index; + block.error = read_pattern(parser); + parser.eat('}', true); + block.expression = { + type: 'Identifier', + name: '', + start: expression.start, + end: prev_index - 7 + }; + block.catch = block.pending; + block.pending = null; + } else { + parser.eat('}', true); // rerun to produce the parser error + } + } + parser.stack.push(block); return; diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js index 4e02f06de6..e7576af955 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/bracket.js @@ -4,6 +4,8 @@ const SQUARE_BRACKET_OPEN = '['.charCodeAt(0); const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0); const CURLY_BRACKET_OPEN = '{'.charCodeAt(0); const CURLY_BRACKET_CLOSE = '}'.charCodeAt(0); +const PARENTHESES_OPEN = '('.charCodeAt(0); +const PARENTHESES_CLOSE = ')'.charCodeAt(0); /** @param {number} code */ export function is_bracket_open(code) { @@ -34,6 +36,9 @@ export function get_bracket_close(open) { if (open === CURLY_BRACKET_OPEN) { return CURLY_BRACKET_CLOSE; } + if (open === PARENTHESES_OPEN) { + return PARENTHESES_CLOSE; + } } /** diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 042e88fa2f..c0e4a65571 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -243,13 +243,15 @@ export function analyze_module(ast, options) { } } + const analysis = { runes: true, tracing: false }; + walk( /** @type {Node} */ (ast), { scope, scopes, // @ts-expect-error TODO - analysis: { runes: true } + analysis }, visitors ); @@ -259,7 +261,8 @@ export function analyze_module(ast, options) { name: options.filename, accessors: false, runes: true, - immutable: true + immutable: true, + tracing: analysis.tracing }; } @@ -408,6 +411,7 @@ export function analyze_component(root, source, options) { template, elements: [], runes, + tracing: false, immutable: runes || options.immutable, exports: [], uses_props: false, diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index 6a301726b1..9f51cd61de 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -171,6 +171,8 @@ export function CallExpression(node, context) { context.state.scope.tracing = b.thunk(b.literal(label + ' ' + loc)); } + + context.state.analysis.tracing = true; } break; diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js index 7454ab8103..9a891c0c49 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js @@ -86,7 +86,23 @@ export function RegularElement(node, context) { (attribute) => attribute.type === 'SpreadAttribute' ); - node.metadata.svg = is_svg(node.name); + const is_svg_element = () => { + if (is_svg(node.name)) { + return true; + } + if (node.name === 'a') { + for (let i = context.path.length - 1; i >= 0; i--) { + const ancestor = context.path[i]; + if (ancestor.type === 'RegularElement') { + return ancestor.metadata.svg; + } + } + } + + return false; + }; + + node.metadata.svg = is_svg_element(); node.metadata.mathml = is_mathml(node.name); if (is_custom_element_node(node) && node.attributes.length > 0) { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index ccbdcea4cc..90901d29ce 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -539,6 +539,10 @@ export function client_component(analysis, options) { body.unshift(b.imports([], 'svelte/internal/flags/legacy')); } + if (analysis.tracing) { + body.unshift(b.imports([], 'svelte/internal/flags/tracing')); + } + if (options.discloseVersion) { body.unshift(b.imports([], 'svelte/internal/disclose-version')); } @@ -667,9 +671,15 @@ export function client_module(analysis, options) { walk(/** @type {AST.SvelteNode} */ (analysis.module.ast), state, visitors) ); + const body = [b.import_all('$', 'svelte/internal/client')]; + + if (analysis.tracing) { + body.push(b.imports([], 'svelte/internal/flags/tracing')); + } + return { type: 'Program', sourceType: 'module', - body: [b.import_all('$', 'svelte/internal/client'), ...module.body] + body: [...body, ...module.body] }; } diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index 8f6597ee12..f96fd64ec7 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -33,12 +33,14 @@ export function transform_component(analysis, source, options) { : client_component(analysis, options); const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte'); + const js = print(program, { // include source content; makes it easier/more robust looking up the source map code // (else esrap does return null for source and sourceMapContent which may trip up tooling) sourceMapContent: source, sourceMapSource: js_source_name }); + merge_with_preprocessor_map(js, options, js_source_name); const css = diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 6a2ea27993..fe32dbba3e 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -27,6 +27,7 @@ export interface Analysis { name: string; // TODO should this be filename? it's used in `compileModule` as well as `compile` runes: boolean; immutable: boolean; + tracing: boolean; // TODO figure out if we can move this to ComponentAnalysis accessors: boolean; @@ -39,6 +40,7 @@ export interface ComponentAnalysis extends Analysis { /** Used for CSS pruning and scoping */ elements: Array; runes: boolean; + tracing: boolean; exports: Array<{ name: string; alias: string | null }>; /** Whether the component uses `$$props` */ uses_props: boolean; diff --git a/packages/svelte/src/internal/client/dev/equality.js b/packages/svelte/src/internal/client/dev/equality.js index 170f7baf95..bbc28dc638 100644 --- a/packages/svelte/src/internal/client/dev/equality.js +++ b/packages/svelte/src/internal/client/dev/equality.js @@ -17,10 +17,11 @@ export function init_array_prototype_warnings() { const index = indexOf.call(this, item, from_index); if (index === -1) { - const test = indexOf.call(get_proxied_value(this), get_proxied_value(item), from_index); - - if (test !== -1) { - w.state_proxy_equality_mismatch('array.indexOf(...)'); + for (let i = from_index ?? 0; i < this.length; i += 1) { + if (get_proxied_value(this[i]) === item) { + w.state_proxy_equality_mismatch('array.indexOf(...)'); + break; + } } } @@ -33,16 +34,11 @@ export function init_array_prototype_warnings() { const index = lastIndexOf.call(this, item, from_index ?? this.length - 1); if (index === -1) { - // we need to specify this.length - 1 because it's probably using something like - // `arguments` inside so passing undefined is different from not passing anything - const test = lastIndexOf.call( - get_proxied_value(this), - get_proxied_value(item), - from_index ?? this.length - 1 - ); - - if (test !== -1) { - w.state_proxy_equality_mismatch('array.lastIndexOf(...)'); + for (let i = 0; i <= (from_index ?? this.length - 1); i += 1) { + if (get_proxied_value(this[i]) === item) { + w.state_proxy_equality_mismatch('array.lastIndexOf(...)'); + break; + } } } @@ -53,10 +49,11 @@ export function init_array_prototype_warnings() { const has = includes.call(this, item, from_index); if (!has) { - const test = includes.call(get_proxied_value(this), get_proxied_value(item), from_index); - - if (test) { - w.state_proxy_equality_mismatch('array.includes(...)'); + for (let i = 0; i < this.length; i += 1) { + if (get_proxied_value(this[i]) === item) { + w.state_proxy_equality_mismatch('array.includes(...)'); + break; + } } } diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index d74f55866f..6cbd6394df 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -14,6 +14,7 @@ import { STATE_SYMBOL, STATE_SYMBOL_METADATA } from './constants.js'; import { UNINITIALIZED } from '../../constants.js'; import * as e from './errors.js'; import { get_stack } from './dev/tracing.js'; +import { tracing_mode_flag } from '../flags/index.js'; /** * @template T @@ -25,7 +26,7 @@ import { get_stack } from './dev/tracing.js'; export function proxy(value, parent = null, prev) { /** @type {Error | null} */ var stack = null; - if (DEV) { + if (DEV && tracing_mode_flag) { stack = get_stack('CreatedAt'); } // if non-proxyable, or is already a proxy, return `value` diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 8c1a3a652b..5e045920bf 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -25,6 +25,7 @@ import * as e from '../errors.js'; import { destroy_effect } from './effects.js'; import { inspect_effects, set_inspect_effects } from './sources.js'; import { get_stack } from '../dev/tracing.js'; +import { tracing_mode_flag } from '../../flags/index.js'; /** * @template V @@ -62,7 +63,7 @@ export function derived(fn) { parent: parent_derived ?? active_effect }; - if (DEV) { + if (DEV && tracing_mode_flag) { signal.created = get_stack('CreatedAt'); } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 24dd837772..3e8c4a00c8 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -32,7 +32,7 @@ import { BLOCK_EFFECT } from '../constants.js'; import * as e from '../errors.js'; -import { legacy_mode_flag } from '../../flags/index.js'; +import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js'; import { get_stack } from '../dev/tracing.js'; export let inspect_effects = new Set(); @@ -60,7 +60,7 @@ export function source(v, stack) { version: 0 }; - if (DEV) { + if (DEV && tracing_mode_flag) { signal.created = stack ?? get_stack('CreatedAt'); signal.debug = null; } @@ -170,7 +170,7 @@ export function internal_set(source, value) { source.v = value; source.version = increment_version(); - if (DEV) { + if (DEV && tracing_mode_flag) { source.updated = get_stack('UpdatedAt'); } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index ff8eaa8ef9..4a90a21971 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -34,7 +34,7 @@ import { destroy_derived, execute_derived, update_derived } from './reactivity/d import * as e from './errors.js'; import { lifecycle_outside_component } from '../shared/errors.js'; import { FILENAME } from '../../constants.js'; -import { legacy_mode_flag } from '../flags/index.js'; +import { legacy_mode_flag, tracing_mode_flag } from '../flags/index.js'; import { tracing_expressions, get_stack } from './dev/tracing.js'; const FLUSH_MICROTASK = 0; @@ -127,8 +127,8 @@ export function set_untracked_writes(value) { untracked_writes = value; } -/** @type {number} Used by sources and deriveds for handling updates to unowned deriveds */ -let current_version = 0; +/** @type {number} Used by sources and deriveds for handling updates to unowned deriveds it starts from 1 to differentiate between a created effect and a run one for tracing */ +let current_version = 1; // If we are working with a get() chain that has no active container, // to prevent memory leaks, we skip adding the reaction. @@ -230,8 +230,9 @@ export function check_dirtiness(reaction) { } } - // Unowned signals should never be marked as clean. - if (!is_unowned) { + // Unowned signals should never be marked as clean unless they + // are used within an active_effect without skip_reaction + if (!is_unowned || (active_effect !== null && !skip_reaction)) { set_signal_status(reaction, CLEAN); } } @@ -916,6 +917,7 @@ export function get(signal) { if ( DEV && + tracing_mode_flag && tracing_expressions !== null && active_reaction !== null && tracing_expressions.reaction === active_reaction diff --git a/packages/svelte/src/internal/flags/index.js b/packages/svelte/src/internal/flags/index.js index 767a40a765..017840f2d9 100644 --- a/packages/svelte/src/internal/flags/index.js +++ b/packages/svelte/src/internal/flags/index.js @@ -1,5 +1,10 @@ export let legacy_mode_flag = false; +export let tracing_mode_flag = false; export function enable_legacy_mode_flag() { legacy_mode_flag = true; } + +export function enable_tracing_mode_flag() { + tracing_mode_flag = true; +} diff --git a/packages/svelte/src/internal/flags/tracing.js b/packages/svelte/src/internal/flags/tracing.js new file mode 100644 index 0000000000..b2de46dcd1 --- /dev/null +++ b/packages/svelte/src/internal/flags/tracing.js @@ -0,0 +1,3 @@ +import { enable_tracing_mode_flag } from './index.js'; + +enable_tracing_mode_flag(); diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 4d4d7e6e86..00070ffab0 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.14.0'; +export const VERSION = '5.15.0'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/input.svelte b/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/input.svelte index a0977b9a63..b64f15c5c1 100644 --- a/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/input.svelte +++ b/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/input.svelte @@ -9,3 +9,15 @@ asd{a.}asd {foo[bar.]} + +{#if x.}{/if} + +{#each array as item (item.)}{/each} + +{#each obj. as item}{/each} + +{#await x.}{/await} + +{#await x. then y}{/await} + +{#await x. catch y}{/await} diff --git a/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/output.json b/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/output.json index d27b6cd914..0564d6d295 100644 --- a/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/output.json +++ b/packages/svelte/tests/parser-legacy/samples/loose-invalid-expression/output.json @@ -2,7 +2,7 @@ "html": { "type": "Fragment", "start": 0, - "end": 164, + "end": 324, "children": [ { "type": "Element", @@ -236,6 +236,272 @@ "end": 163, "name": "" } + }, + { + "type": "Text", + "start": 164, + "end": 166, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "IfBlock", + "start": 166, + "end": 179, + "expression": { + "type": "Identifier", + "start": 171, + "end": 173, + "name": "" + }, + "children": [] + }, + { + "type": "Text", + "start": 179, + "end": 181, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "EachBlock", + "start": 181, + "end": 217, + "children": [], + "context": { + "type": "Identifier", + "name": "item", + "start": 197, + "loc": { + "start": { + "line": 15, + "column": 16, + "character": 197 + }, + "end": { + "line": 15, + "column": 20, + "character": 201 + } + }, + "end": 201 + }, + "expression": { + "type": "Identifier", + "start": 188, + "end": 193, + "loc": { + "start": { + "line": 15, + "column": 7 + }, + "end": { + "line": 15, + "column": 12 + } + }, + "name": "array" + }, + "key": { + "type": "Identifier", + "start": 203, + "end": 208, + "name": "" + } + }, + { + "type": "Text", + "start": 217, + "end": 219, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "EachBlock", + "start": 219, + "end": 246, + "children": [], + "context": { + "type": "Identifier", + "name": "item", + "start": 234, + "loc": { + "start": { + "line": 17, + "column": 15, + "character": 234 + }, + "end": { + "line": 17, + "column": 19, + "character": 238 + } + }, + "end": 238 + }, + "expression": { + "type": "Identifier", + "name": "", + "start": 226, + "end": 230 + } + }, + { + "type": "Text", + "start": 246, + "end": 248, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "AwaitBlock", + "start": 248, + "end": 267, + "expression": { + "type": "Identifier", + "start": 256, + "end": 258, + "name": "" + }, + "value": null, + "error": null, + "pending": { + "type": "PendingBlock", + "start": 259, + "end": 259, + "children": [], + "skip": false + }, + "then": { + "type": "ThenBlock", + "start": null, + "end": null, + "children": [], + "skip": true + }, + "catch": { + "type": "CatchBlock", + "start": null, + "end": null, + "children": [], + "skip": true + } + }, + { + "type": "Text", + "start": 267, + "end": 269, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "AwaitBlock", + "start": 269, + "end": 295, + "expression": { + "type": "Identifier", + "name": "", + "start": 277, + "end": 279 + }, + "value": { + "type": "Identifier", + "name": "y", + "start": 285, + "loc": { + "start": { + "line": 21, + "column": 16, + "character": 285 + }, + "end": { + "line": 21, + "column": 17, + "character": 286 + } + }, + "end": 286 + }, + "error": null, + "pending": { + "type": "PendingBlock", + "start": null, + "end": null, + "children": [], + "skip": true + }, + "then": { + "type": "ThenBlock", + "start": 287, + "end": 267, + "children": [], + "skip": false + }, + "catch": { + "type": "CatchBlock", + "start": null, + "end": null, + "children": [], + "skip": true + } + }, + { + "type": "Text", + "start": 295, + "end": 297, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "AwaitBlock", + "start": 297, + "end": 324, + "expression": { + "type": "Identifier", + "name": "", + "start": 305, + "end": 307 + }, + "value": null, + "error": { + "type": "Identifier", + "name": "y", + "start": 314, + "loc": { + "start": { + "line": 23, + "column": 17, + "character": 314 + }, + "end": { + "line": 23, + "column": 18, + "character": 315 + } + }, + "end": 315 + }, + "pending": { + "type": "PendingBlock", + "start": null, + "end": null, + "children": [], + "skip": true + }, + "then": { + "type": "ThenBlock", + "start": null, + "end": null, + "children": [], + "skip": true + }, + "catch": { + "type": "CatchBlock", + "start": 316, + "end": 295, + "children": [], + "skip": false + } } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte index 10a3876b7c..83017c79aa 100644 --- a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte +++ b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/input.svelte @@ -10,6 +10,14 @@ +
+ + +
+ + {#if foo}
{/if} diff --git a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json index d91cc68098..2205a00e20 100644 --- a/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json +++ b/packages/svelte/tests/parser-legacy/samples/loose-unclosed-tag/output.json @@ -2,7 +2,7 @@ "html": { "type": "Fragment", "start": 0, - "end": 160, + "end": 204, "children": [ { "type": "Element", @@ -136,20 +136,82 @@ "data": "\n\n" }, { - "type": "IfBlock", + "type": "Element", "start": 74, + "end": 94, + "name": "div", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 79, + "end": 81, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "InlineComponent", + "start": 81, + "end": 88, + "name": "Comp.", + "attributes": [], + "children": [] + } + ] + }, + { + "type": "Text", + "start": 94, "end": 96, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "Element", + "start": 96, + "end": 116, + "name": "div", + "attributes": [], + "children": [ + { + "type": "Text", + "start": 101, + "end": 103, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "InlineComponent", + "start": 103, + "end": 110, + "name": "comp.", + "attributes": [], + "children": [] + } + ] + }, + { + "type": "Text", + "start": 116, + "end": 118, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "IfBlock", + "start": 118, + "end": 140, "expression": { "type": "Identifier", - "start": 79, - "end": 82, + "start": 123, + "end": 126, "loc": { "start": { - "line": 13, + "line": 21, "column": 5 }, "end": { - "line": 13, + "line": 21, "column": 8 } }, @@ -158,15 +220,15 @@ "children": [ { "type": "Element", - "start": 85, - "end": 91, + "start": 129, + "end": 135, "name": "div", "attributes": [], "children": [ { "type": "Text", - "start": 90, - "end": 91, + "start": 134, + "end": 135, "raw": "\n", "data": "\n" } @@ -176,26 +238,26 @@ }, { "type": "Text", - "start": 96, - "end": 98, + "start": 140, + "end": 142, "raw": "\n\n", "data": "\n\n" }, { "type": "IfBlock", - "start": 98, - "end": 130, + "start": 142, + "end": 174, "expression": { "type": "Identifier", - "start": 103, - "end": 106, + "start": 147, + "end": 150, "loc": { "start": { - "line": 17, + "line": 25, "column": 5 }, "end": { - "line": 17, + "line": 25, "column": 8 } }, @@ -204,31 +266,31 @@ "children": [ { "type": "InlineComponent", - "start": 109, - "end": 125, + "start": 153, + "end": 169, "name": "Comp", "attributes": [ { "type": "Attribute", - "start": 115, - "end": 124, + "start": 159, + "end": 168, "name": "foo", "value": [ { "type": "MustacheTag", - "start": 119, - "end": 124, + "start": 163, + "end": 168, "expression": { "type": "Identifier", - "start": 120, - "end": 123, + "start": 164, + "end": 167, "loc": { "start": { - "line": 18, + "line": 26, "column": 12 }, "end": { - "line": 18, + "line": 26, "column": 15 } }, @@ -244,36 +306,36 @@ }, { "type": "Text", - "start": 130, - "end": 132, + "start": 174, + "end": 176, "raw": "\n\n", "data": "\n\n" }, { "type": "Element", - "start": 132, - "end": 160, + "start": 176, + "end": 204, "name": "div", "attributes": [], "children": [ { "type": "Text", - "start": 137, - "end": 138, + "start": 181, + "end": 182, "raw": "\n", "data": "\n" }, { "type": "Element", - "start": 138, - "end": 147, + "start": 182, + "end": 191, "name": "p", "attributes": [], "children": [ { "type": "Text", - "start": 141, - "end": 143, + "start": 185, + "end": 187, "raw": "hi", "data": "hi" } @@ -281,15 +343,15 @@ }, { "type": "Text", - "start": 147, - "end": 149, + "start": 191, + "end": 193, "raw": "\n\n", "data": "\n\n" }, { "type": "Element", - "start": 149, - "end": 160, + "start": 193, + "end": 204, "name": "open-ended", "attributes": [], "children": [] diff --git a/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/input.svelte b/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/input.svelte index a0977b9a63..b64f15c5c1 100644 --- a/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/input.svelte +++ b/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/input.svelte @@ -9,3 +9,15 @@ asd{a.}asd {foo[bar.]} + +{#if x.}{/if} + +{#each array as item (item.)}{/each} + +{#each obj. as item}{/each} + +{#await x.}{/await} + +{#await x. then y}{/await} + +{#await x. catch y}{/await} diff --git a/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/output.json b/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/output.json index cdb7f66c58..56fa4286dd 100644 --- a/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/output.json +++ b/packages/svelte/tests/parser-modern/samples/loose-invalid-expression/output.json @@ -2,7 +2,7 @@ "css": null, "js": [], "start": 0, - "end": 164, + "end": 324, "type": "Root", "fragment": { "type": "Fragment", @@ -247,6 +247,238 @@ "end": 163, "name": "" } + }, + { + "type": "Text", + "start": 164, + "end": 166, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "IfBlock", + "elseif": false, + "start": 166, + "end": 179, + "test": { + "type": "Identifier", + "start": 171, + "end": 173, + "name": "" + }, + "consequent": { + "type": "Fragment", + "nodes": [] + }, + "alternate": null + }, + { + "type": "Text", + "start": 179, + "end": 181, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "EachBlock", + "start": 181, + "end": 217, + "expression": { + "type": "Identifier", + "start": 188, + "end": 193, + "loc": { + "start": { + "line": 15, + "column": 7 + }, + "end": { + "line": 15, + "column": 12 + } + }, + "name": "array" + }, + "body": { + "type": "Fragment", + "nodes": [] + }, + "context": { + "type": "Identifier", + "name": "item", + "start": 197, + "loc": { + "start": { + "line": 15, + "column": 16, + "character": 197 + }, + "end": { + "line": 15, + "column": 20, + "character": 201 + } + }, + "end": 201 + }, + "key": { + "type": "Identifier", + "start": 203, + "end": 208, + "name": "" + } + }, + { + "type": "Text", + "start": 217, + "end": 219, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "EachBlock", + "start": 219, + "end": 246, + "expression": { + "type": "Identifier", + "name": "", + "start": 226, + "end": 230 + }, + "body": { + "type": "Fragment", + "nodes": [] + }, + "context": { + "type": "Identifier", + "name": "item", + "start": 234, + "loc": { + "start": { + "line": 17, + "column": 15, + "character": 234 + }, + "end": { + "line": 17, + "column": 19, + "character": 238 + } + }, + "end": 238 + } + }, + { + "type": "Text", + "start": 246, + "end": 248, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "AwaitBlock", + "start": 248, + "end": 267, + "expression": { + "type": "Identifier", + "start": 256, + "end": 258, + "name": "" + }, + "value": null, + "error": null, + "pending": { + "type": "Fragment", + "nodes": [] + }, + "then": null, + "catch": null + }, + { + "type": "Text", + "start": 267, + "end": 269, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "AwaitBlock", + "start": 269, + "end": 295, + "expression": { + "type": "Identifier", + "name": "", + "start": 277, + "end": 279 + }, + "value": { + "type": "Identifier", + "name": "y", + "start": 285, + "loc": { + "start": { + "line": 21, + "column": 16, + "character": 285 + }, + "end": { + "line": 21, + "column": 17, + "character": 286 + } + }, + "end": 286 + }, + "error": null, + "pending": null, + "then": { + "type": "Fragment", + "nodes": [] + }, + "catch": null + }, + { + "type": "Text", + "start": 295, + "end": 297, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "AwaitBlock", + "start": 297, + "end": 324, + "expression": { + "type": "Identifier", + "name": "", + "start": 305, + "end": 307 + }, + "value": null, + "error": { + "type": "Identifier", + "name": "y", + "start": 314, + "loc": { + "start": { + "line": 23, + "column": 17, + "character": 314 + }, + "end": { + "line": 23, + "column": 18, + "character": 315 + } + }, + "end": 315 + }, + "pending": null, + "then": null, + "catch": { + "type": "Fragment", + "nodes": [] + } } ] }, diff --git a/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/input.svelte b/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/input.svelte index 10a3876b7c..83017c79aa 100644 --- a/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/input.svelte +++ b/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/input.svelte @@ -10,6 +10,14 @@ +
+ + +
+ + {#if foo}
{/if} diff --git a/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/output.json b/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/output.json index ec47bfc738..cf9138c026 100644 --- a/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/output.json +++ b/packages/svelte/tests/parser-modern/samples/loose-unclosed-tag/output.json @@ -2,7 +2,7 @@ "css": null, "js": [], "start": 0, - "end": 160, + "end": 204, "type": "Root", "fragment": { "type": "Fragment", @@ -155,21 +155,95 @@ "data": "\n\n" }, { - "type": "IfBlock", - "elseif": false, + "type": "RegularElement", "start": 74, + "end": 94, + "name": "div", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 79, + "end": 81, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "Component", + "start": 81, + "end": 88, + "name": "Comp.", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [] + } + } + ] + } + }, + { + "type": "Text", + "start": 94, "end": 96, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "RegularElement", + "start": 96, + "end": 116, + "name": "div", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "Text", + "start": 101, + "end": 103, + "raw": "\n\t", + "data": "\n\t" + }, + { + "type": "Component", + "start": 103, + "end": 110, + "name": "comp.", + "attributes": [], + "fragment": { + "type": "Fragment", + "nodes": [] + } + } + ] + } + }, + { + "type": "Text", + "start": 116, + "end": 118, + "raw": "\n\n", + "data": "\n\n" + }, + { + "type": "IfBlock", + "elseif": false, + "start": 118, + "end": 140, "test": { "type": "Identifier", - "start": 79, - "end": 82, + "start": 123, + "end": 126, "loc": { "start": { - "line": 13, + "line": 21, "column": 5 }, "end": { - "line": 13, + "line": 21, "column": 8 } }, @@ -180,15 +254,15 @@ "nodes": [ { "type": "Text", - "start": 83, - "end": 85, + "start": 127, + "end": 129, "raw": "\n\t", "data": "\n\t" }, { "type": "RegularElement", - "start": 85, - "end": 91, + "start": 129, + "end": 135, "name": "div", "attributes": [], "fragment": { @@ -196,8 +270,8 @@ "nodes": [ { "type": "Text", - "start": 90, - "end": 91, + "start": 134, + "end": 135, "raw": "\n", "data": "\n" } @@ -210,27 +284,27 @@ }, { "type": "Text", - "start": 96, - "end": 98, + "start": 140, + "end": 142, "raw": "\n\n", "data": "\n\n" }, { "type": "IfBlock", "elseif": false, - "start": 98, - "end": 130, + "start": 142, + "end": 174, "test": { "type": "Identifier", - "start": 103, - "end": 106, + "start": 147, + "end": 150, "loc": { "start": { - "line": 17, + "line": 25, "column": 5 }, "end": { - "line": 17, + "line": 25, "column": 8 } }, @@ -241,37 +315,37 @@ "nodes": [ { "type": "Text", - "start": 107, - "end": 109, + "start": 151, + "end": 153, "raw": "\n\t", "data": "\n\t" }, { "type": "Component", - "start": 109, - "end": 125, + "start": 153, + "end": 169, "name": "Comp", "attributes": [ { "type": "Attribute", - "start": 115, - "end": 124, + "start": 159, + "end": 168, "name": "foo", "value": { "type": "ExpressionTag", - "start": 119, - "end": 124, + "start": 163, + "end": 168, "expression": { "type": "Identifier", - "start": 120, - "end": 123, + "start": 164, + "end": 167, "loc": { "start": { - "line": 18, + "line": 26, "column": 12 }, "end": { - "line": 18, + "line": 26, "column": 15 } }, @@ -291,15 +365,15 @@ }, { "type": "Text", - "start": 130, - "end": 132, + "start": 174, + "end": 176, "raw": "\n\n", "data": "\n\n" }, { "type": "RegularElement", - "start": 132, - "end": 160, + "start": 176, + "end": 204, "name": "div", "attributes": [], "fragment": { @@ -307,15 +381,15 @@ "nodes": [ { "type": "Text", - "start": 137, - "end": 138, + "start": 181, + "end": 182, "raw": "\n", "data": "\n" }, { "type": "RegularElement", - "start": 138, - "end": 147, + "start": 182, + "end": 191, "name": "p", "attributes": [], "fragment": { @@ -323,8 +397,8 @@ "nodes": [ { "type": "Text", - "start": 141, - "end": 143, + "start": 185, + "end": 187, "raw": "hi", "data": "hi" } @@ -333,15 +407,15 @@ }, { "type": "Text", - "start": 147, - "end": 149, + "start": 191, + "end": 193, "raw": "\n\n", "data": "\n\n" }, { "type": "RegularElement", - "start": 149, - "end": 160, + "start": 193, + "end": 204, "name": "open-ended", "attributes": [], "fragment": { diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js index 517e11d74e..efa5985e4e 100644 --- a/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace/_config.js @@ -11,12 +11,15 @@ function normalise_trace_logs(logs) { if (typeof log === 'string' && log.includes('%c')) { const split = log.split('%c'); - normalised.push((split[0].length !== 0 ? split[0] : split[1]).trim()); + normalised.push({ + log: (split[0].length !== 0 ? split[0] : split[1]).trim(), + highlighted: logs[i + 1] === 'color: CornflowerBlue; font-weight: bold' + }); i++; } else if (log instanceof Error) { continue; } else { - normalised.push(log); + normalised.push({ log }); } } return normalised; @@ -28,7 +31,17 @@ export default test({ }, test({ assert, target, logs }) { - assert.deepEqual(normalise_trace_logs(logs), ['effect', '$derived', 0, '$state', 0]); + // initial log, everything is highlighted + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$derived', highlighted: true }, + { log: 0 }, + { log: '$state', highlighted: true }, + { log: 0 }, + { log: '$state', highlighted: true }, + { log: false } + ]); logs.length = 0; @@ -36,6 +49,49 @@ export default test({ button?.click(); flushSync(); - assert.deepEqual(normalise_trace_logs(logs), ['effect', '$derived', 2, '$state', 1]); + // count changed, derived and state are highlighted, last state is not + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$derived', highlighted: true }, + { log: 2 }, + { log: '$state', highlighted: true }, + { log: 1 }, + { log: '$state', highlighted: false }, + { log: false } + ]); + + logs.length = 0; + + const input = target.querySelector('input'); + input?.click(); + flushSync(); + + // checked changed, last state is highlighted, first two are not + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$derived', highlighted: false }, + { log: 2 }, + { log: '$state', highlighted: false }, + { log: 1 }, + { log: '$state', highlighted: true }, + { log: true } + ]); + + logs.length = 0; + + button?.click(); + flushSync(); + + // count change and derived it's >=4, checked is not in the dependencies anymore + + assert.deepEqual(normalise_trace_logs(logs), [ + { log: 'effect', highlighted: false }, + { log: '$derived', highlighted: true }, + { log: 4 }, + { log: '$state', highlighted: true }, + { log: 2 } + ]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte index 99c30a07bf..99f246aa73 100644 --- a/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace/main.svelte @@ -2,11 +2,15 @@ let count = $state(0); let double = $derived(count * 2); + let checked = $state(false); + $effect(() => { $inspect.trace('effect'); double; - }) + double >= 4 || checked; + }); + diff --git a/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/_config.js b/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/_config.js new file mode 100644 index 0000000000..0fb086c8e0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/_config.js @@ -0,0 +1,41 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + async test({ assert, target, warnings }) { + const [btn1, btn2, btn3, btn4, btn5, btn6, clear] = target.querySelectorAll('button'); + + flushSync(() => { + btn1.click(); + btn2.click(); + btn3.click(); + btn4.click(); + btn5.click(); + btn6.click(); + }); + + assert.deepEqual(warnings, [ + 'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.includes(...)` will produce unexpected results', + 'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.indexOf(...)` will produce unexpected results', + 'Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `array.lastIndexOf(...)` will produce unexpected results' + ]); + + flushSync(() => clear.click()); + warnings.length = 0; + + flushSync(() => { + btn1.click(); + btn2.click(); + btn3.click(); + btn4.click(); + btn5.click(); + btn6.click(); + }); + + assert.deepEqual(warnings, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/main.svelte b/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/main.svelte new file mode 100644 index 0000000000..1b7bf444f1 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/state-proxy-equality-mismatch/main.svelte @@ -0,0 +1,23 @@ + + + + + +
+ + + + +
+ + + + +
+ + diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/_config.js b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/_config.js new file mode 100644 index 0000000000..7373a4043e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/_config.js @@ -0,0 +1,11 @@ +import { test, ok } from '../../test'; + +export default test({ + html: ``, + test({ assert, target }) { + const a = target.querySelector('a'); + ok(a); + + assert.equal(a.namespaceURI, 'http://www.w3.org/2000/svg'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/main.svelte new file mode 100644 index 0000000000..3676278c6c --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block-2/main.svelte @@ -0,0 +1,7 @@ + + {#if true} + + {name} + + {/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte b/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte index 3b98dafa05..cd23b31096 100644 --- a/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/typescript/main.svelte @@ -8,6 +8,10 @@ console.log(this); } + function foo(): string { + return ""!; + } + class Foo { public name: string; x = 'x' as const; @@ -16,6 +20,8 @@ } } + class MyClass implements Hello {} + declare const declared_const: number; declare function declared_fn(): void; declare class declared_class { @@ -24,7 +30,7 @@ declare module 'foobar' {} namespace SomeNamespace { - export type Foo = true + export type Foo = true; } export function overload(a: boolean): boolean; diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index 380307901e..c9f0fb3b2b 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -40,5 +40,9 @@ "./tests/runtime-browser/test-ssr.ts", "./tests/*/samples/*/_config.js" ], - "exclude": ["./scripts/process-messages/templates/", "./src/compiler/optimizer/"] + "exclude": [ + "./scripts/process-messages/templates/", + "./scripts/_bundle.js", + "./src/compiler/optimizer/" + ] } diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 671f68bff7..d422abebbc 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2996,6 +2996,29 @@ declare namespace $effect { */ declare function $props(): any; +declare namespace $props { + // prevent intellisense from being unhelpful + /** @deprecated */ + export const apply: never; + /** @deprecated */ + // @ts-ignore + export const arguments: never; + /** @deprecated */ + export const bind: never; + /** @deprecated */ + export const call: never; + /** @deprecated */ + export const caller: never; + /** @deprecated */ + export const length: never; + /** @deprecated */ + export const name: never; + /** @deprecated */ + export const prototype: never; + /** @deprecated */ + export const toString: never; +} + /** * Declares a prop as bindable, meaning the parent component can use `bind:propName={value}` to bind to it. * @@ -3007,6 +3030,29 @@ declare function $props(): any; */ declare function $bindable(fallback?: T): T; +declare namespace $bindable { + // prevent intellisense from being unhelpful + /** @deprecated */ + export const apply: never; + /** @deprecated */ + // @ts-ignore + export const arguments: never; + /** @deprecated */ + export const bind: never; + /** @deprecated */ + export const call: never; + /** @deprecated */ + export const caller: never; + /** @deprecated */ + export const length: never; + /** @deprecated */ + export const name: never; + /** @deprecated */ + export const prototype: never; + /** @deprecated */ + export const toString: never; +} + /** * Inspects one or more values whenever they, or the properties they contain, change. Example: * @@ -3046,6 +3092,27 @@ declare namespace $inspect { * */ export function trace(name: string): void; + + // prevent intellisense from being unhelpful + /** @deprecated */ + export const apply: never; + /** @deprecated */ + // @ts-ignore + export const arguments: never; + /** @deprecated */ + export const bind: never; + /** @deprecated */ + export const call: never; + /** @deprecated */ + export const caller: never; + /** @deprecated */ + export const length: never; + /** @deprecated */ + export const name: never; + /** @deprecated */ + export const prototype: never; + /** @deprecated */ + export const toString: never; } /** @@ -3069,4 +3136,27 @@ declare namespace $inspect { */ declare function $host(): El; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file +declare namespace $host { + // prevent intellisense from being unhelpful + /** @deprecated */ + export const apply: never; + /** @deprecated */ + // @ts-ignore + export const arguments: never; + /** @deprecated */ + export const bind: never; + /** @deprecated */ + export const call: never; + /** @deprecated */ + export const caller: never; + /** @deprecated */ + export const length: never; + /** @deprecated */ + export const name: never; + /** @deprecated */ + export const prototype: never; + /** @deprecated */ + export const toString: never; +} + +//# sourceMappingURL=index.d.ts.map diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 250a03744e..f7f38700e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,8 +84,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 esrap: - specifier: ^1.2.3 - version: 1.2.3 + specifier: ^1.3.2 + version: 1.3.2 is-reference: specifier: ^3.0.3 version: 3.0.3 @@ -908,7 +908,7 @@ packages: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} concat-map@0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -1111,8 +1111,8 @@ packages: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} - esrap@1.2.3: - resolution: {integrity: sha512-ZlQmCCK+n7SGoqo7DnfKaP1sJZa49P01/dXzmjCASSo04p72w8EksT2NMK8CEX8DhKsfJXANioIw8VyHNsBfvQ==} + esrap@1.3.2: + resolution: {integrity: sha512-C4PXusxYhFT98GjLSmb20k9PREuUdporer50dhzGuJu9IJXktbMddVCMLAERl5dAHyAi73GWWCE4FVHGP1794g==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -3315,10 +3315,9 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@1.2.3: + esrap@1.3.2: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 esrecurse@4.3.0: dependencies: