fix conflict

aa
Dominic Gannaway 7 months ago
commit a79dfb5a19

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: do not prune selectors like `:global(.foo):has(.scoped)`

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: don't error on slot prop inside block inside other component

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: ensure reactions are correctly attached for unowned deriveds

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: silence a11y attribute warnings when spread attributes present

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent false-positive ownership validations due to hot reload

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: widen ownership when calling setContext

@ -339,13 +339,18 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element)
let sibling_elements; // do them lazy because it's rarely used and expensive to calculate let sibling_elements; // do them lazy because it's rarely used and expensive to calculate
// If this is a :has inside a global selector, we gotta include the element itself, too, // If this is a :has inside a global selector, we gotta include the element itself, too,
// because the global selector might be for an element that's outside the component (e.g. :root). // because the global selector might be for an element that's outside the component,
// e.g. :root:has(.scoped), :global(.foo):has(.scoped), or :root { &:has(.scoped) {} }
const rules = get_parent_rules(rule); const rules = get_parent_rules(rule);
const include_self = const include_self =
rules.some((r) => r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))) || rules.some((r) => r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))) ||
rules[rules.length - 1].prelude.children.some((c) => rules[rules.length - 1].prelude.children.some((c) =>
c.children.some((r) => c.children.some((r) =>
r.selectors.some((s) => s.type === 'PseudoClassSelector' && s.name === 'root') r.selectors.some(
(s) =>
s.type === 'PseudoClassSelector' &&
(s.name === 'root' || (s.name === 'global' && s.args))
)
) )
); );
if (include_self) { if (include_self) {

@ -756,7 +756,8 @@ export function check_element(node, context) {
name === 'aria-activedescendant' && name === 'aria-activedescendant' &&
!is_dynamic_element && !is_dynamic_element &&
!is_interactive_element(node.name, attribute_map) && !is_interactive_element(node.name, attribute_map) &&
!attribute_map.has('tabindex') !attribute_map.has('tabindex') &&
!has_spread
) { ) {
w.a11y_aria_activedescendant_has_tabindex(attribute); w.a11y_aria_activedescendant_has_tabindex(attribute);
} }
@ -810,9 +811,9 @@ export function check_element(node, context) {
const role = roles_map.get(current_role); const role = roles_map.get(current_role);
if (role) { if (role) {
const required_role_props = Object.keys(role.requiredProps); const required_role_props = Object.keys(role.requiredProps);
const has_missing_props = required_role_props.some( const has_missing_props =
(prop) => !attributes.find((a) => a.name === prop) !has_spread &&
); required_role_props.some((prop) => !attributes.find((a) => a.name === prop));
if (has_missing_props) { if (has_missing_props) {
w.a11y_role_has_required_aria_props( w.a11y_role_has_required_aria_props(
attribute, attribute,
@ -828,6 +829,7 @@ export function check_element(node, context) {
// interactive-supports-focus // interactive-supports-focus
if ( if (
!has_spread &&
!has_disabled_attribute(attribute_map) && !has_disabled_attribute(attribute_map) &&
!is_hidden_from_screen_reader(node.name, attribute_map) && !is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(current_role) && !is_presentation_role(current_role) &&
@ -845,6 +847,7 @@ export function check_element(node, context) {
// no-interactive-element-to-noninteractive-role // no-interactive-element-to-noninteractive-role
if ( if (
!has_spread &&
is_interactive_element(node.name, attribute_map) && is_interactive_element(node.name, attribute_map) &&
(is_non_interactive_roles(current_role) || is_presentation_role(current_role)) (is_non_interactive_roles(current_role) || is_presentation_role(current_role))
) { ) {
@ -853,6 +856,7 @@ export function check_element(node, context) {
// no-noninteractive-element-to-interactive-role // no-noninteractive-element-to-interactive-role
if ( if (
!has_spread &&
is_non_interactive_element(node.name, attribute_map) && is_non_interactive_element(node.name, attribute_map) &&
is_interactive_roles(current_role) && is_interactive_roles(current_role) &&
!a11y_non_interactive_element_to_interactive_role_exceptions[node.name]?.includes( !a11y_non_interactive_element_to_interactive_role_exceptions[node.name]?.includes(
@ -947,6 +951,7 @@ export function check_element(node, context) {
// no-noninteractive-element-interactions // no-noninteractive-element-interactions
if ( if (
!has_spread &&
!has_contenteditable_attr && !has_contenteditable_attr &&
!is_hidden_from_screen_reader(node.name, attribute_map) && !is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(role_static_value) && !is_presentation_role(role_static_value) &&
@ -964,6 +969,7 @@ export function check_element(node, context) {
// no-static-element-interactions // no-static-element-interactions
if ( if (
!has_spread &&
(!role || role_static_value !== null) && (!role || role_static_value !== null) &&
!is_hidden_from_screen_reader(node.name, attribute_map) && !is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(role_static_value) && !is_presentation_role(role_static_value) &&
@ -981,11 +987,11 @@ export function check_element(node, context) {
} }
} }
if (handlers.has('mouseover') && !handlers.has('focus')) { if (!has_spread && handlers.has('mouseover') && !handlers.has('focus')) {
w.a11y_mouse_events_have_key_events(node, 'mouseover', 'focus'); w.a11y_mouse_events_have_key_events(node, 'mouseover', 'focus');
} }
if (handlers.has('mouseout') && !handlers.has('blur')) { if (!has_spread && handlers.has('mouseout') && !handlers.has('blur')) {
w.a11y_mouse_events_have_key_events(node, 'mouseout', 'blur'); w.a11y_mouse_events_have_key_events(node, 'mouseout', 'blur');
} }
@ -995,7 +1001,7 @@ export function check_element(node, context) {
if (node.name === 'a' || node.name === 'button') { if (node.name === 'a' || node.name === 'button') {
const is_hidden = get_static_value(attribute_map.get('aria-hidden')) === 'true'; const is_hidden = get_static_value(attribute_map.get('aria-hidden')) === 'true';
if (!is_hidden && !is_labelled && !has_content(node)) { if (!has_spread && !is_hidden && !is_labelled && !has_content(node)) {
w.a11y_consider_explicit_label(node); w.a11y_consider_explicit_label(node);
} }
} }
@ -1054,7 +1060,7 @@ export function check_element(node, context) {
if (node.name === 'img') { if (node.name === 'img') {
const alt_attribute = get_static_text_value(attribute_map.get('alt')); const alt_attribute = get_static_text_value(attribute_map.get('alt'));
const aria_hidden = get_static_value(attribute_map.get('aria-hidden')); const aria_hidden = get_static_value(attribute_map.get('aria-hidden'));
if (alt_attribute && !aria_hidden) { if (alt_attribute && !aria_hidden && !has_spread) {
if (/\b(image|picture|photo)\b/i.test(alt_attribute)) { if (/\b(image|picture|photo)\b/i.test(alt_attribute)) {
w.a11y_img_redundant_alt(node); w.a11y_img_redundant_alt(node);
} }
@ -1087,7 +1093,7 @@ export function check_element(node, context) {
); );
return has; return has;
}; };
if (!attribute_map.has('for') && !has_input_child(node)) { if (!has_spread && !attribute_map.has('for') && !has_input_child(node)) {
w.a11y_label_has_associated_control(node); w.a11y_label_has_associated_control(node);
} }
} }
@ -1095,7 +1101,7 @@ export function check_element(node, context) {
if (node.name === 'video') { if (node.name === 'video') {
const aria_hidden_attribute = attribute_map.get('aria-hidden'); const aria_hidden_attribute = attribute_map.get('aria-hidden');
const aria_hidden_exist = aria_hidden_attribute && get_static_value(aria_hidden_attribute); const aria_hidden_exist = aria_hidden_attribute && get_static_value(aria_hidden_attribute);
if (attribute_map.has('muted') || aria_hidden_exist === 'true') { if (attribute_map.has('muted') || aria_hidden_exist === 'true' || has_spread) {
return; return;
} }
let has_caption = false; let has_caption = false;
@ -1141,6 +1147,7 @@ export function check_element(node, context) {
// Check content // Check content
if ( if (
!has_spread &&
!is_labelled && !is_labelled &&
!has_contenteditable_binding && !has_contenteditable_binding &&
a11y_required_content.includes(node.name) && a11y_required_content.includes(node.name) &&

@ -80,18 +80,19 @@ export function validate_slot_attribute(context, attribute, is_component = false
} }
if (owner) { if (owner) {
if (!is_text_attribute(attribute)) {
e.slot_attribute_invalid(attribute);
}
if ( if (
owner.type === 'Component' || owner.type === 'Component' ||
owner.type === 'SvelteComponent' || owner.type === 'SvelteComponent' ||
owner.type === 'SvelteSelf' owner.type === 'SvelteSelf'
) { ) {
if (owner !== parent) { if (owner !== parent) {
if (!is_component) {
e.slot_attribute_invalid_placement(attribute); e.slot_attribute_invalid_placement(attribute);
} }
} else {
if (!is_text_attribute(attribute)) {
e.slot_attribute_invalid(attribute);
}
const name = attribute.value[0].data; const name = attribute.value[0].data;
@ -117,6 +118,7 @@ export function validate_slot_attribute(context, attribute, is_component = false
} }
} }
} }
}
} else if (!is_component) { } else if (!is_component) {
e.slot_attribute_invalid_placement(attribute); e.slot_attribute_invalid_placement(attribute);
} }

@ -8,7 +8,8 @@ import {
active_effect, active_effect,
active_reaction, active_reaction,
set_active_effect, set_active_effect,
set_active_reaction set_active_reaction,
untrack
} from './runtime.js'; } from './runtime.js';
import { effect } from './reactivity/effects.js'; import { effect } from './reactivity/effects.js';
import { legacy_mode_flag } from '../flags/index.js'; import { legacy_mode_flag } from '../flags/index.js';
@ -49,14 +50,6 @@ export function set_dev_current_component_function(fn) {
export function getContext(key) { export function getContext(key) {
const context_map = get_or_init_context_map('getContext'); const context_map = get_or_init_context_map('getContext');
const result = /** @type {T} */ (context_map.get(key)); const result = /** @type {T} */ (context_map.get(key));
if (DEV) {
const fn = /** @type {ComponentContext} */ (component_context).function;
if (fn) {
add_owner(result, fn, true);
}
}
return result; return result;
} }
@ -74,6 +67,15 @@ export function getContext(key) {
*/ */
export function setContext(key, context) { export function setContext(key, context) {
const context_map = get_or_init_context_map('setContext'); const context_map = get_or_init_context_map('setContext');
if (DEV) {
// When state is put into context, we treat as if it's global from now on.
// We do for performance reasons (it's for example very expensive to call
// getContext on a big object many times when part of a list component)
// and danger of false positives.
untrack(() => add_owner(context, null, true));
}
context_map.set(key, context); context_map.set(key, context);
return context; return context;
} }
@ -100,16 +102,6 @@ export function hasContext(key) {
*/ */
export function getAllContexts() { export function getAllContexts() {
const context_map = get_or_init_context_map('getAllContexts'); const context_map = get_or_init_context_map('getAllContexts');
if (DEV) {
const fn = component_context?.function;
if (fn) {
for (const value of context_map.values()) {
add_owner(value, fn, true);
}
}
}
return /** @type {T} */ (context_map); return /** @type {T} */ (context_map);
} }

@ -109,7 +109,7 @@ export function mark_module_end(component) {
/** /**
* @param {any} object * @param {any} object
* @param {any} owner * @param {any | null} owner
* @param {boolean} [global] * @param {boolean} [global]
* @param {boolean} [skip_warning] * @param {boolean} [skip_warning]
*/ */
@ -120,7 +120,7 @@ export function add_owner(object, owner, global = false, skip_warning = false) {
if (metadata && !has_owner(metadata, component)) { if (metadata && !has_owner(metadata, component)) {
let original = get_owner(metadata); let original = get_owner(metadata);
if (owner[FILENAME] !== component[FILENAME] && !skip_warning) { if (owner && owner[FILENAME] !== component[FILENAME] && !skip_warning) {
w.ownership_invalid_binding(component[FILENAME], owner[FILENAME], original[FILENAME]); w.ownership_invalid_binding(component[FILENAME], owner[FILENAME], original[FILENAME]);
} }
} }
@ -165,7 +165,7 @@ export function widen_ownership(from, to) {
/** /**
* @param {any} object * @param {any} object
* @param {Function} owner * @param {Function | null} owner If `null`, then the object is globally owned and will not be checked
* @param {Set<any>} seen * @param {Set<any>} seen
*/ */
function add_owner_to_object(object, owner, seen) { function add_owner_to_object(object, owner, seen) {
@ -174,7 +174,11 @@ function add_owner_to_object(object, owner, seen) {
if (metadata) { if (metadata) {
// this is a state proxy, add owner directly, if not globally shared // this is a state proxy, add owner directly, if not globally shared
if ('owners' in metadata && metadata.owners != null) { if ('owners' in metadata && metadata.owners != null) {
if (owner) {
metadata.owners.add(owner); metadata.owners.add(owner);
} else {
metadata.owners = null;
}
} }
} else if (object && typeof object === 'object') { } else if (object && typeof object === 'object') {
if (seen.has(object)) return; if (seen.has(object)) return;
@ -216,6 +220,10 @@ function has_owner(metadata, component) {
return ( return (
metadata.owners.has(component) || metadata.owners.has(component) ||
// This helps avoid false positives when using HMR, where the component function is replaced
[...metadata.owners].some(
(owner) => /** @type {any} */ (owner)[FILENAME] === /** @type {any} */ (component)?.[FILENAME]
) ||
(metadata.parent !== null && has_owner(metadata.parent, component)) (metadata.parent !== null && has_owner(metadata.parent, component))
); );
} }

@ -833,7 +833,12 @@ function process_effects(effect, collected_effects) {
if (is_branch) { if (is_branch) {
current_effect.f ^= CLEAN; current_effect.f ^= CLEAN;
} else if (!skip_suspended) { } else if (!skip_suspended) {
// Ensure we set the effect to be the active reaction
// to ensure that unowned deriveds are correctly tracked
// because we're flushing the current effect
var previous_active_reaction = active_reaction;
try { try {
active_reaction = current_effect;
if (check_dirtiness(current_effect)) { if (check_dirtiness(current_effect)) {
update_effect(current_effect); update_effect(current_effect);
if ((flags & IS_ASYNC) !== 0 && !suspended) { if ((flags & IS_ASYNC) !== 0 && !suspended) {
@ -842,6 +847,8 @@ function process_effects(effect, collected_effects) {
} }
} catch (error) { } catch (error) {
handle_error(error, current_effect, null, current_effect.ctx); handle_error(error, current_effect, null, current_effect.ctx);
} finally {
active_reaction = previous_active_reaction;
} }
} }
@ -1013,15 +1020,13 @@ export function get(signal) {
var derived = /** @type {Derived} */ (signal); var derived = /** @type {Derived} */ (signal);
var parent = derived.parent; var parent = derived.parent;
if (parent !== null) { if (parent !== null && (parent.f & UNOWNED) === 0) {
// If the derived is owned by another derived then mark it as unowned // If the derived is owned by another derived then mark it as unowned
// as the derived value might have been referenced in a different context // as the derived value might have been referenced in a different context
// since and thus its parent might not be its true owner anymore // since and thus its parent might not be its true owner anymore
if ((parent.f & UNOWNED) === 0) {
derived.f ^= UNOWNED; derived.f ^= UNOWNED;
} }
} }
}
if (is_derived) { if (is_derived) {
derived = /** @type {Derived} */ (signal); derived = /** @type {Derived} */ (signal);

@ -197,6 +197,20 @@ export default test({
column: 16, column: 16,
character: 1614 character: 1614
} }
},
{
code: 'css_unused_selector',
message: 'Unused CSS selector ":global(.foo):has(.unused)"',
start: {
line: 155,
column: 1,
character: 1684
},
end: {
line: 155,
column: 27,
character: 1710
}
} }
] ]
}); });

@ -136,3 +136,10 @@
color: red; color: red;
}*/ }*/
} }
.foo:has(x.svelte-xyz) {
color: green;
}
/* (unused) :global(.foo):has(.unused) {
color: red;
}*/

@ -148,4 +148,11 @@
color: red; color: red;
} }
} }
:global(.foo):has(x) {
color: green;
}
:global(.foo):has(.unused) {
color: red;
}
</style> </style>

@ -0,0 +1,5 @@
<script>
let { value = $bindable() } = $props()
</script>
<button onclick={() => value = 'a'}>change</button>

@ -0,0 +1,5 @@
<script>
let {disabled = false} = $props()
</script>
{disabled}

@ -0,0 +1,16 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
let [btn1, btn2] = target.querySelectorAll('button');
btn1?.click();
flushSync();
btn2?.click();
flushSync();
assert.htmlEqual(target.innerHTML, `<button>change</button><button>change</button>\nfalse`);
}
});

@ -0,0 +1,11 @@
<script>
import Child2 from './Child2.svelte'
import Child from './Child.svelte'
let loginname = $state('')
let password = $state('')
</script>
<Child bind:value={loginname} />
<Child bind:value={password} />
<Child2 disabled={!loginname || !password} />

@ -10,3 +10,4 @@
<label>E <span></span></label> <label>E <span></span></label>
<label>F {#if true}<input type="text" />{/if}</label> <label>F {#if true}<input type="text" />{/if}</label>
<LabelComponent>G <input type="text" /></LabelComponent> <LabelComponent>G <input type="text" /></LabelComponent>
<label {...forMightBeInHere}>E <span></span></label>

@ -1,21 +1,19 @@
<script> <script>
// Even if otherProps contains onBlur and/or onFocus, the rule will still fail.
// Props should be passed down explicitly for rule to pass.
const otherProps = { const otherProps = {
onBlur: () => void 0, onblur: () => {},
onFocus: () => void 0 onfocus: () => {}
}; };
</script> </script>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseover={() => void 0}></div> <div onmouseover={() => {}}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseover={() => void 0} on:focus={() => void 0}></div> <div onmouseover={() => {}} onfocus={() => {}}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseover={() => void 0} {...otherProps}></div> <div onmouseover={() => {}} {...otherProps}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseout={() => void 0}></div> <div onmouseout={() => {}}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseout={() => void 0} on:blur={() => void 0}></div> <div onmouseout={() => {}} onblur={() => {}}></div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseout={() => void 0} {...otherProps}></div> <div onmouseout={() => {}} {...otherProps}></div>

@ -2,49 +2,25 @@
{ {
"code": "a11y_mouse_events_have_key_events", "code": "a11y_mouse_events_have_key_events",
"end": { "end": {
"column": 39, "column": 34,
"line": 11 "line": 9
}, },
"message": "'mouseover' event must be accompanied by 'focus' event", "message": "'mouseover' event must be accompanied by 'focus' event",
"start": { "start": {
"column": 0, "column": 0,
"line": 11 "line": 9
} }
}, },
{ {
"code": "a11y_mouse_events_have_key_events", "code": "a11y_mouse_events_have_key_events",
"end": { "end": {
"column": 55, "column": 33,
"line": 15 "line": 15
}, },
"message": "'mouseover' event must be accompanied by 'focus' event",
"start": {
"column": 0,
"line": 15
}
},
{
"code": "a11y_mouse_events_have_key_events",
"end": {
"column": 38,
"line": 17
},
"message": "'mouseout' event must be accompanied by 'blur' event", "message": "'mouseout' event must be accompanied by 'blur' event",
"start": { "start": {
"column": 0, "column": 0,
"line": 17 "line": 15
}
},
{
"code": "a11y_mouse_events_have_key_events",
"end": {
"column": 54,
"line": 21
},
"message": "'mouseout' event must be accompanied by 'blur' event",
"start": {
"column": 0,
"line": 21
} }
} }
] ]

@ -1,2 +1,8 @@
<Foo slot="foo">valid</Foo> <Foo slot="foo">valid</Foo>
<Foo slot={foo}>valid</Foo> <Foo slot={foo}>valid</Foo>
<Foo>
{#if true}
<Foo slot="foo">valid</Foo>
<Foo slot={foo}>valid</Foo>
{/if}
</Foo>
Loading…
Cancel
Save