diff --git a/.changeset/purple-eagles-enjoy.md b/.changeset/purple-eagles-enjoy.md deleted file mode 100644 index 318f85a946..0000000000 --- a/.changeset/purple-eagles-enjoy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: use symbols for encapsulated event delegation diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 542ae4c8f6..4e4f4f6088 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,33 @@ # svelte +## 5.51.3 + +### Patch Changes + +- fix: prevent event delegation logic conflicting between svelte instances ([#17728](https://github.com/sveltejs/svelte/pull/17728)) + +- fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes ([#17712](https://github.com/sveltejs/svelte/pull/17712)) + +- fix: locate Rollup annontaion friendly to JS downgraders ([#17724](https://github.com/sveltejs/svelte/pull/17724)) + +- fix: run effects in pending snippets ([#17719](https://github.com/sveltejs/svelte/pull/17719)) + +## 5.51.2 + +### Patch Changes + +- fix: take async into consideration for dev delegated handlers ([#17710](https://github.com/sveltejs/svelte/pull/17710)) + +- fix: emit state_referenced_locally warning for non-destructured props ([#17708](https://github.com/sveltejs/svelte/pull/17708)) + +## 5.51.1 + +### Patch Changes + +- fix: don't crash on undefined `document.contentType` ([#17707](https://github.com/sveltejs/svelte/pull/17707)) + +- fix: use symbols for encapsulated event delegation ([#17703](https://github.com/sveltejs/svelte/pull/17703)) + ## 5.51.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 3b650af156..e9f4713d1c 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.51.0", + "version": "5.51.3", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index 7242c69d8c..7e05d2e7d3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -22,6 +22,50 @@ const whitelist_attribute_selector = new Map([ ['dialog', ['open']] ]); +/** + * HTML attributes whose enumerated values are case-insensitive per the HTML spec. + * CSS attribute selectors match these values case-insensitively in HTML documents. + * @see {@link https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors HTML spec} + */ +const case_insensitive_attributes = new Set([ + 'accept-charset', + 'autocapitalize', + 'autocomplete', + 'behavior', + 'charset', + 'crossorigin', + 'decoding', + 'dir', + 'direction', + 'draggable', + 'enctype', + 'enterkeyhint', + 'fetchpriority', + 'formenctype', + 'formmethod', + 'formtarget', + 'hidden', + 'http-equiv', + 'inputmode', + 'kind', + 'loading', + 'method', + 'preload', + 'referrerpolicy', + 'rel', + 'rev', + 'role', + 'rules', + 'scope', + 'shape', + 'spellcheck', + 'target', + 'translate', + 'type', + 'valign', + 'wrap' +]); + /** @type {Compiler.AST.CSS.Combinator} */ const descendant_combinator = { type: 'Combinator', @@ -523,7 +567,9 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element, selector.name, selector.value && unquote(selector.value), selector.matcher, - selector.flags?.includes('i') ?? false + (selector.flags?.includes('i') ?? false) || + (!selector.flags?.includes('s') && + case_insensitive_attributes.has(selector.name.toLowerCase())) ) ) { return false; 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 9daae33dd7..5c1e8031b8 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -115,7 +115,8 @@ export function Identifier(node, context) { !should_proxy(binding.initial.arguments[0], context.state.scope)))) || binding.kind === 'raw_state' || binding.kind === 'derived' || - binding.kind === 'prop') && + binding.kind === 'prop' || + binding.kind === 'rest_prop') && // We're only concerned with reads here (parent.type !== 'AssignmentExpression' || parent.left !== node) && parent.type !== 'UpdateExpression' diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js index 9485a33b3d..f01bf08694 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/events.js @@ -70,7 +70,8 @@ export function build_event(context, event_name, handler, capture, passive, dele fn = b.function( b.id(name), handler.params, - handler.body.type === 'BlockStatement' ? handler.body : b.block([b.return(handler.body)]) + handler.body.type === 'BlockStatement' ? handler.body : b.block([b.return(handler.body)]), + handler.async ); } diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index 524f35bccf..3525539f1d 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -67,7 +67,10 @@ export const STALE_REACTION = new (class StaleReactionError extends Error { message = 'The reaction that called `getAbortSignal()` was re-run or destroyed'; })(); -export const IS_XHTML = /* @__PURE__ */ globalThis.document?.contentType.includes('xml') ?? false; +export const IS_XHTML = + // We gotta write it like this because after downleveling the pure comment may end up in the wrong location + !!globalThis.document?.contentType && + /* @__PURE__ */ globalThis.document.contentType.includes('xml'); export const ELEMENT_NODE = 1; export const TEXT_NODE = 3; export const COMMENT_NODE = 8; diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 44bd0f22f4..419e8dc201 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -1,6 +1,5 @@ /** @import { Effect, Source, TemplateNode, } from '#client' */ import { - BLOCK_EFFECT, BOUNDARY_EFFECT, COMMENT_NODE, DIRTY, @@ -53,7 +52,7 @@ import { set_signal_status } from '../../reactivity/status.js'; * }} BoundaryProps */ -var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT; +var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED; /** * @param {TemplateNode} node @@ -69,6 +68,10 @@ export class Boundary { /** @type {Boundary | null} */ parent; + /** + * True if the boundary has a `pending` snippet, is not yet resolved, and either + * a) is mounting or b) is hydrating a server-rendered `pending` snippet + */ is_pending = false; /** @type {TemplateNode} */ @@ -98,15 +101,10 @@ export class Boundary { /** @type {DocumentFragment | null} */ #offscreen_fragment = null; - /** @type {TemplateNode | null} */ - #pending_anchor = null; - #local_pending_count = 0; #pending_count = 0; #pending_count_update_queued = false; - #is_creating_fallback = false; - /** @type {Set} */ #dirty_effects = new Set(); @@ -142,51 +140,31 @@ export class Boundary { constructor(node, props, children) { this.#anchor = node; this.#props = props; - this.#children = children; - this.parent = /** @type {Effect} */ (active_effect).b; + this.#children = (anchor) => { + var effect = /** @type {Effect} */ (active_effect); - this.is_pending = !!this.#props.pending; + effect.b = this; + effect.f |= BOUNDARY_EFFECT; - this.#effect = block(() => { - /** @type {Effect} */ (active_effect).b = this; + children(anchor); + }; + + this.parent = /** @type {Effect} */ (active_effect).b; + this.#effect = block(() => { if (hydrating) { - const comment = this.#hydrate_open; + const comment = /** @type {Comment} */ (this.#hydrate_open); hydrate_next(); - const server_rendered_pending = - /** @type {Comment} */ (comment).nodeType === COMMENT_NODE && - /** @type {Comment} */ (comment).data === HYDRATION_START_ELSE; - - if (server_rendered_pending) { + if (comment.data === HYDRATION_START_ELSE) { this.#hydrate_pending_content(); } else { this.#hydrate_resolved_content(); - - if (this.#pending_count === 0) { - this.is_pending = false; - } } } else { - var anchor = this.#get_anchor(); - - try { - this.#main_effect = branch(() => children(anchor)); - } catch (error) { - this.error(error); - } - - if (this.#pending_count > 0) { - this.#show_pending_snippet(); - } else { - this.is_pending = false; - } + this.#render(); } - - return () => { - this.#pending_anchor?.remove(); - }; }, flags); if (hydrating) { @@ -206,19 +184,24 @@ export class Boundary { const pending = this.#props.pending; if (!pending) return; + this.is_pending = true; this.#pending_effect = branch(() => pending(this.#anchor)); queue_micro_task(() => { - var anchor = this.#get_anchor(); + var fragment = (this.#offscreen_fragment = document.createDocumentFragment()); + var anchor = create_text(); + + fragment.append(anchor); this.#main_effect = this.#run(() => { Batch.ensure(); return branch(() => this.#children(anchor)); }); - if (this.#pending_count > 0) { - this.#show_pending_snippet(); - } else { + if (this.#pending_count === 0) { + this.#anchor.before(fragment); + this.#offscreen_fragment = null; + pause_effect(/** @type {Effect} */ (this.#pending_effect), () => { this.#pending_effect = null; }); @@ -228,17 +211,28 @@ export class Boundary { }); } - #get_anchor() { - var anchor = this.#anchor; + #render() { + try { + this.is_pending = this.has_pending_snippet(); + this.#pending_count = 0; + this.#local_pending_count = 0; + + this.#main_effect = branch(() => { + this.#children(this.#anchor); + }); - if (this.is_pending) { - this.#pending_anchor = create_text(); - this.#anchor.before(this.#pending_anchor); + if (this.#pending_count > 0) { + var fragment = (this.#offscreen_fragment = document.createDocumentFragment()); + move_effect(this.#main_effect, fragment); - anchor = this.#pending_anchor; + const pending = /** @type {(anchor: Node) => void} */ (this.#props.pending); + this.#pending_effect = branch(() => pending(this.#anchor)); + } else { + this.is_pending = false; + } + } catch (error) { + this.error(error); } - - return anchor; } /** @@ -262,7 +256,8 @@ export class Boundary { } /** - * @param {() => Effect | null} fn + * @template T + * @param {() => T} fn */ #run(fn) { var previous_effect = active_effect; @@ -285,20 +280,6 @@ export class Boundary { } } - #show_pending_snippet() { - const pending = /** @type {(anchor: Node) => void} */ (this.#props.pending); - - if (this.#main_effect !== null) { - this.#offscreen_fragment = document.createDocumentFragment(); - this.#offscreen_fragment.append(/** @type {TemplateNode} */ (this.#pending_anchor)); - move_effect(this.#main_effect, this.#offscreen_fragment); - } - - if (this.#pending_effect === null) { - this.#pending_effect = branch(() => pending(this.#anchor)); - } - } - #reschedule_deferred_effects() { this.is_pending = false; @@ -387,7 +368,7 @@ export class Boundary { // If we have nothing to capture the error, or if we hit an error while // rendering the fallback, re-throw for another boundary to handle - if (this.#is_creating_fallback || (!onerror && !failed)) { + if (!onerror && !failed) { throw error; } @@ -427,31 +408,18 @@ export class Boundary { e.svelte_boundary_reset_onerror(); } - // If the failure happened while flushing effects, current_batch can be null - Batch.ensure(); - - this.#local_pending_count = 0; - if (this.#failed_effect !== null) { pause_effect(this.#failed_effect, () => { this.#failed_effect = null; }); } - // we intentionally do not try to find the nearest pending boundary. If this boundary has one, we'll render it on reset - // but it would be really weird to show the parent's boundary on a child reset. - this.is_pending = this.has_pending_snippet(); + this.#run(() => { + // If the failure happened while flushing effects, current_batch can be null + Batch.ensure(); - this.#main_effect = this.#run(() => { - this.#is_creating_fallback = false; - return branch(() => this.#children(this.#anchor)); + this.#render(); }); - - if (this.#pending_count > 0) { - this.#show_pending_snippet(); - } else { - this.is_pending = false; - } }; queue_micro_task(() => { @@ -466,10 +434,16 @@ export class Boundary { if (failed) { this.#failed_effect = this.#run(() => { Batch.ensure(); - this.#is_creating_fallback = true; try { return branch(() => { + // errors in `failed` snippets cause the boundary to error again + // TODO Svelte 6: revisit this decision, most likely better to go to parent boundary instead + var effect = /** @type {Effect} */ (active_effect); + + effect.b = this; + effect.f |= BOUNDARY_EFFECT; + failed( this.#anchor, () => error, @@ -479,8 +453,6 @@ export class Boundary { } catch (error) { invoke_error_boundary(error, /** @type {Effect} */ (this.#effect.parent)); return null; - } finally { - this.#is_creating_fallback = false; } }); } diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 041698eb9d..e598a78949 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -11,8 +11,11 @@ import { set_active_reaction } from '../../runtime.js'; import { without_reactive_context } from './bindings/shared.js'; -import { can_delegate_event } from '../../../../utils.js'; +/** + * Used on elements, as a map of event type -> event handler, + * and on events themselves to track which element handled an event + */ export const event_symbol = Symbol('events'); /** @type {Set} */ @@ -177,8 +180,8 @@ export function handle_event_propagation(event) { last_propagated_event = event; // composedPath contains list of nodes the event has propagated through. - // We check __root to skip all nodes below it in case this is a - // parent of the __root node, which indicates that there's nested + // We check `event_symbol` to skip all nodes below it in case this is a + // parent of the `event_symbol` node, which indicates that there's nested // mounted apps. In this case we don't want to trigger events multiple times. var path_idx = 0; @@ -186,7 +189,7 @@ export function handle_event_propagation(event) { // without it the variable will be DCE'd and things will // fail mysteriously in Firefox // @ts-expect-error is added below - var handled_at = last_propagated_event === event && event.__root; + var handled_at = last_propagated_event === event && event[event_symbol]; if (handled_at) { var at_idx = path.indexOf(handled_at); @@ -198,7 +201,7 @@ export function handle_event_propagation(event) { // -> ignore, but set handle_at to document/window so that we're resetting the event // chain in case someone manually dispatches the same event object again. // @ts-expect-error - event.__root = handler_element; + event[event_symbol] = handler_element; return; } @@ -298,7 +301,7 @@ export function handle_event_propagation(event) { } } finally { // @ts-expect-error is used above - event.__root = handler_element; + event[event_symbol] = handler_element; // @ts-ignore remove proxy on currentTarget delete event.currentTarget; set_active_reaction(previous_reaction); diff --git a/packages/svelte/src/internal/client/dom/reconciler.js b/packages/svelte/src/internal/client/dom/reconciler.js index 1f4750ce8f..0ceebd3337 100644 --- a/packages/svelte/src/internal/client/dom/reconciler.js +++ b/packages/svelte/src/internal/client/dom/reconciler.js @@ -2,15 +2,15 @@ import { create_element } from './operations.js'; -const policy = /* @__PURE__ */ globalThis?.window?.trustedTypes?.createPolicy( - 'svelte-trusted-html', - { +const policy = + // We gotta write it like this because after downleveling the pure comment may end up in the wrong location + globalThis?.window?.trustedTypes && + /* @__PURE__ */ globalThis.window.trustedTypes.createPolicy('svelte-trusted-html', { /** @param {string} html */ createHTML: (html) => { return html; } - } -); + }); /** @param {string} html */ function create_trusted_html(html) { diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index 82a2d4f484..b382a4e3a5 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -293,16 +293,19 @@ export class Batch { } } - var parent = effect.parent; - effect = effect.next; - - while (effect === null && parent !== null) { - if (parent === pending_boundary) { + while (effect !== null) { + if (effect === pending_boundary) { pending_boundary = null; } - effect = parent.next; - parent = parent.parent; + var next = effect.next; + + if (next !== null) { + effect = next; + break; + } + + effect = effect.parent; } } } diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 0d5bc6cb49..76a73852d5 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -161,48 +161,6 @@ const listeners = new Map(); function _mount(Component, { target, anchor, props = {}, events, context, intro = true }) { init_operations(); - /** @type {Set} */ - var registered_events = new Set(); - - /** @param {Array} events */ - var event_handle = (events) => { - for (var i = 0; i < events.length; i++) { - var event_name = events[i]; - - if (registered_events.has(event_name)) continue; - registered_events.add(event_name); - - var passive = is_passive_event(event_name); - - // Add the event listener to both the container and the document. - // The container listener ensures we catch events from within in case - // the outer content stops propagation of the event. - // - // The document listener ensures we catch events that originate from elements that were - // manually moved outside of the container (e.g. via manual portals). - for (const node of [target, document]) { - var counts = listeners.get(node); - - if (counts === undefined) { - counts = new Map(); - listeners.set(node, counts); - } - - var count = counts.get(event_name); - - if (count === undefined) { - node.addEventListener(event_name, handle_event_propagation, { passive }); - counts.set(event_name, 1); - } else { - counts.set(event_name, count + 1); - } - } - } - }; - - event_handle(array_from(all_registered_events)); - root_event_handles.add(event_handle); - /** @type {Exports} */ // @ts-expect-error will be defined because the render effect runs synchronously var component = undefined; @@ -251,6 +209,49 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro } ); + // Setup event delegation _after_ component is mounted - if an error would happen during mount, it would otherwise not be cleaned up + /** @type {Set} */ + var registered_events = new Set(); + + /** @param {Array} events */ + var event_handle = (events) => { + for (var i = 0; i < events.length; i++) { + var event_name = events[i]; + + if (registered_events.has(event_name)) continue; + registered_events.add(event_name); + + var passive = is_passive_event(event_name); + + // Add the event listener to both the container and the document. + // The container listener ensures we catch events from within in case + // the outer content stops propagation of the event. + // + // The document listener ensures we catch events that originate from elements that were + // manually moved outside of the container (e.g. via manual portals). + for (const node of [target, document]) { + var counts = listeners.get(node); + + if (counts === undefined) { + counts = new Map(); + listeners.set(node, counts); + } + + var count = counts.get(event_name); + + if (count === undefined) { + node.addEventListener(event_name, handle_event_propagation, { passive }); + counts.set(event_name, 1); + } else { + counts.set(event_name, count + 1); + } + } + } + }; + + event_handle(array_from(all_registered_events)); + root_event_handles.add(event_handle); + return () => { for (var event_name of registered_events) { for (const node of [target, document]) { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 688c22df03..c27b247dba 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.51.0'; +export const VERSION = '5.51.3'; export const PUBLIC_VERSION = '5'; diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css new file mode 100644 index 0000000000..01ec1b2269 --- /dev/null +++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css @@ -0,0 +1,11 @@ + form[method="get"].svelte-xyz h1:where(.svelte-xyz) { + color: red; + } + + form[method="post"].svelte-xyz h1:where(.svelte-xyz) { + color: blue; + } + + input[type="text"].svelte-xyz { + color: green; + } diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html new file mode 100644 index 0000000000..101c03f836 --- /dev/null +++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html @@ -0,0 +1 @@ +

Hello

World

diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte new file mode 100644 index 0000000000..e11a922d75 --- /dev/null +++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte @@ -0,0 +1,23 @@ +
+

Hello

+
+ +
+

World

+
+ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/_config.js b/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/_config.js new file mode 100644 index 0000000000..eb5b2a1675 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/_config.js @@ -0,0 +1,9 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + async test() {} +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/main.svelte new file mode 100644 index 0000000000..bffcad6059 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-handler-async-delegated/main.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.warnings.json b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.warnings.json new file mode 100644 index 0000000000..81149a92f8 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/client/index.svelte.warnings.json @@ -0,0 +1,142 @@ +[ + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally", + "filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte", + "start": { + "line": 3, + "column": 1, + "character": 33 + }, + "end": { + "line": 3, + "column": 6, + "character": 38 + }, + "position": [ + 33, + 38 + ], + "frame": "1: " + }, + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally", + "filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte", + "start": { + "line": 9, + "column": 1, + "character": 120 + }, + "end": { + "line": 9, + "column": 6, + "character": 125 + }, + "position": [ + 120, + 125 + ], + "frame": " 7: props.a = true;\n 8: props[a] = true;\n 9: props;\n ^\n10: \n11: " + } +] \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.warnings.json b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.warnings.json new file mode 100644 index 0000000000..81149a92f8 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/props-identifier/_expected/server/index.svelte.warnings.json @@ -0,0 +1,142 @@ +[ + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally", + "filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte", + "start": { + "line": 3, + "column": 1, + "character": 33 + }, + "end": { + "line": 3, + "column": 6, + "character": 38 + }, + "position": [ + 33, + 38 + ], + "frame": "1: " + }, + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally", + "filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte", + "start": { + "line": 9, + "column": 1, + "character": 120 + }, + "end": { + "line": 9, + "column": 6, + "character": 125 + }, + "position": [ + 120, + 125 + ], + "frame": " 7: props.a = true;\n 8: props[a] = true;\n 9: props;\n ^\n10: \n11: " + } +] \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/input.svelte b/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/input.svelte new file mode 100644 index 0000000000..980debdfaf --- /dev/null +++ b/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/input.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/warnings.json b/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/warnings.json new file mode 100644 index 0000000000..b1385037e1 --- /dev/null +++ b/packages/svelte/tests/validator/samples/state-referenced-locally-props-identifier/warnings.json @@ -0,0 +1,26 @@ +[ + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?", + "start": { + "line": 3, + "column": 19 + }, + "end": { + "line": 3, + "column": 24 + } + }, + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?", + "start": { + "line": 4, + "column": 15 + }, + "end": { + "line": 4, + "column": 20 + } + } +]