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
// 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 include_self =
rules.some((r) => r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))) ||
rules[rules.length - 1].prelude.children.some((c) =>
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) {

@ -756,7 +756,8 @@ export function check_element(node, context) {
name === 'aria-activedescendant' &&
!is_dynamic_element &&
!is_interactive_element(node.name, attribute_map) &&
!attribute_map.has('tabindex')
!attribute_map.has('tabindex') &&
!has_spread
) {
w.a11y_aria_activedescendant_has_tabindex(attribute);
}
@ -810,9 +811,9 @@ export function check_element(node, context) {
const role = roles_map.get(current_role);
if (role) {
const required_role_props = Object.keys(role.requiredProps);
const has_missing_props = required_role_props.some(
(prop) => !attributes.find((a) => a.name === prop)
);
const has_missing_props =
!has_spread &&
required_role_props.some((prop) => !attributes.find((a) => a.name === prop));
if (has_missing_props) {
w.a11y_role_has_required_aria_props(
attribute,
@ -828,6 +829,7 @@ export function check_element(node, context) {
// interactive-supports-focus
if (
!has_spread &&
!has_disabled_attribute(attribute_map) &&
!is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(current_role) &&
@ -845,6 +847,7 @@ export function check_element(node, context) {
// no-interactive-element-to-noninteractive-role
if (
!has_spread &&
is_interactive_element(node.name, attribute_map) &&
(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
if (
!has_spread &&
is_non_interactive_element(node.name, attribute_map) &&
is_interactive_roles(current_role) &&
!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
if (
!has_spread &&
!has_contenteditable_attr &&
!is_hidden_from_screen_reader(node.name, attribute_map) &&
!is_presentation_role(role_static_value) &&
@ -964,6 +969,7 @@ export function check_element(node, context) {
// no-static-element-interactions
if (
!has_spread &&
(!role || role_static_value !== null) &&
!is_hidden_from_screen_reader(node.name, attribute_map) &&
!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');
}
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');
}
@ -995,7 +1001,7 @@ export function check_element(node, context) {
if (node.name === 'a' || node.name === 'button') {
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);
}
}
@ -1054,7 +1060,7 @@ export function check_element(node, context) {
if (node.name === 'img') {
const alt_attribute = get_static_text_value(attribute_map.get('alt'));
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)) {
w.a11y_img_redundant_alt(node);
}
@ -1087,7 +1093,7 @@ export function check_element(node, context) {
);
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);
}
}
@ -1095,7 +1101,7 @@ export function check_element(node, context) {
if (node.name === 'video') {
const aria_hidden_attribute = attribute_map.get('aria-hidden');
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;
}
let has_caption = false;
@ -1141,6 +1147,7 @@ export function check_element(node, context) {
// Check content
if (
!has_spread &&
!is_labelled &&
!has_contenteditable_binding &&
a11y_required_content.includes(node.name) &&

@ -80,40 +80,42 @@ export function validate_slot_attribute(context, attribute, is_component = false
}
if (owner) {
if (!is_text_attribute(attribute)) {
e.slot_attribute_invalid(attribute);
}
if (
owner.type === 'Component' ||
owner.type === 'SvelteComponent' ||
owner.type === 'SvelteSelf'
) {
if (owner !== parent) {
e.slot_attribute_invalid_placement(attribute);
}
if (!is_component) {
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;
if (context.state.component_slots.has(name)) {
e.slot_attribute_duplicate(attribute, name, owner.name);
}
context.state.component_slots.add(name);
if (context.state.component_slots.has(name)) {
e.slot_attribute_duplicate(attribute, name, owner.name);
}
if (name === 'default') {
for (const node of owner.fragment.nodes) {
if (node.type === 'Text' && regex_only_whitespaces.test(node.data)) {
continue;
}
context.state.component_slots.add(name);
if (node.type === 'RegularElement' || node.type === 'SvelteFragment') {
if (node.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')) {
if (name === 'default') {
for (const node of owner.fragment.nodes) {
if (node.type === 'Text' && regex_only_whitespaces.test(node.data)) {
continue;
}
}
e.slot_default_duplicate(node);
if (node.type === 'RegularElement' || node.type === 'SvelteFragment') {
if (node.attributes.some((a) => a.type === 'Attribute' && a.name === 'slot')) {
continue;
}
}
e.slot_default_duplicate(node);
}
}
}
}

@ -8,7 +8,8 @@ import {
active_effect,
active_reaction,
set_active_effect,
set_active_reaction
set_active_reaction,
untrack
} from './runtime.js';
import { effect } from './reactivity/effects.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) {
const context_map = get_or_init_context_map('getContext');
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;
}
@ -74,6 +67,15 @@ export function getContext(key) {
*/
export function setContext(key, context) {
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);
return context;
}
@ -100,16 +102,6 @@ export function hasContext(key) {
*/
export function 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);
}

@ -109,7 +109,7 @@ export function mark_module_end(component) {
/**
* @param {any} object
* @param {any} owner
* @param {any | null} owner
* @param {boolean} [global]
* @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)) {
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]);
}
}
@ -165,7 +165,7 @@ export function widen_ownership(from, to) {
/**
* @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
*/
function add_owner_to_object(object, owner, seen) {
@ -174,7 +174,11 @@ function add_owner_to_object(object, owner, seen) {
if (metadata) {
// this is a state proxy, add owner directly, if not globally shared
if ('owners' in metadata && metadata.owners != null) {
metadata.owners.add(owner);
if (owner) {
metadata.owners.add(owner);
} else {
metadata.owners = null;
}
}
} else if (object && typeof object === 'object') {
if (seen.has(object)) return;
@ -216,6 +220,10 @@ function has_owner(metadata, component) {
return (
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))
);
}

@ -833,7 +833,12 @@ function process_effects(effect, collected_effects) {
if (is_branch) {
current_effect.f ^= CLEAN;
} 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 {
active_reaction = current_effect;
if (check_dirtiness(current_effect)) {
update_effect(current_effect);
if ((flags & IS_ASYNC) !== 0 && !suspended) {
@ -842,6 +847,8 @@ function process_effects(effect, collected_effects) {
}
} catch (error) {
handle_error(error, current_effect, null, current_effect.ctx);
} finally {
active_reaction = previous_active_reaction;
}
}
@ -1013,13 +1020,11 @@ export function get(signal) {
var derived = /** @type {Derived} */ (signal);
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
// as the derived value might have been referenced in a different context
// since and thus its parent might not be its true owner anymore
if ((parent.f & UNOWNED) === 0) {
derived.f ^= UNOWNED;
}
derived.f ^= UNOWNED;
}
}

@ -197,6 +197,20 @@ export default test({
column: 16,
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;
}*/
}
.foo:has(x.svelte-xyz) {
color: green;
}
/* (unused) :global(.foo):has(.unused) {
color: red;
}*/

@ -148,4 +148,11 @@
color: red;
}
}
:global(.foo):has(x) {
color: green;
}
:global(.foo):has(.unused) {
color: red;
}
</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>F {#if true}<input type="text" />{/if}</label>
<LabelComponent>G <input type="text" /></LabelComponent>
<label {...forMightBeInHere}>E <span></span></label>

@ -1,21 +1,19 @@
<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 = {
onBlur: () => void 0,
onFocus: () => void 0
onblur: () => {},
onfocus: () => {}
};
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseover={() => void 0}></div>
<div onmouseover={() => {}}></div>
<!-- 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 -->
<div on:mouseover={() => void 0} {...otherProps}></div>
<div onmouseover={() => {}} {...otherProps}></div>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:mouseout={() => void 0}></div>
<div onmouseout={() => {}}></div>
<!-- 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 -->
<div on:mouseout={() => void 0} {...otherProps}></div>
<div onmouseout={() => {}} {...otherProps}></div>

@ -2,49 +2,25 @@
{
"code": "a11y_mouse_events_have_key_events",
"end": {
"column": 39,
"line": 11
"column": 34,
"line": 9
},
"message": "'mouseover' event must be accompanied by 'focus' event",
"start": {
"column": 0,
"line": 11
"line": 9
}
},
{
"code": "a11y_mouse_events_have_key_events",
"end": {
"column": 55,
"column": 33,
"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",
"start": {
"column": 0,
"line": 17
}
},
{
"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
"line": 15
}
}
]

@ -1,2 +1,8 @@
<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