Skip to content

Commit b8607f8

Browse files
authored
fix: silence a11y attribute warnings when spread attributes present (#15150)
Fixes #15067
1 parent f5406c9 commit b8607f8

File tree

5 files changed

+36
-49
lines changed

5 files changed

+36
-49
lines changed

.changeset/odd-rules-hear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: silence a11y attribute warnings when spread attributes present

packages/svelte/src/compiler/phases/2-analyze/visitors/shared/a11y.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,8 @@ export function check_element(node, context) {
756756
name === 'aria-activedescendant' &&
757757
!is_dynamic_element &&
758758
!is_interactive_element(node.name, attribute_map) &&
759-
!attribute_map.has('tabindex')
759+
!attribute_map.has('tabindex') &&
760+
!has_spread
760761
) {
761762
w.a11y_aria_activedescendant_has_tabindex(attribute);
762763
}
@@ -810,9 +811,9 @@ export function check_element(node, context) {
810811
const role = roles_map.get(current_role);
811812
if (role) {
812813
const required_role_props = Object.keys(role.requiredProps);
813-
const has_missing_props = required_role_props.some(
814-
(prop) => !attributes.find((a) => a.name === prop)
815-
);
814+
const has_missing_props =
815+
!has_spread &&
816+
required_role_props.some((prop) => !attributes.find((a) => a.name === prop));
816817
if (has_missing_props) {
817818
w.a11y_role_has_required_aria_props(
818819
attribute,
@@ -828,6 +829,7 @@ export function check_element(node, context) {
828829

829830
// interactive-supports-focus
830831
if (
832+
!has_spread &&
831833
!has_disabled_attribute(attribute_map) &&
832834
!is_hidden_from_screen_reader(node.name, attribute_map) &&
833835
!is_presentation_role(current_role) &&
@@ -845,6 +847,7 @@ export function check_element(node, context) {
845847

846848
// no-interactive-element-to-noninteractive-role
847849
if (
850+
!has_spread &&
848851
is_interactive_element(node.name, attribute_map) &&
849852
(is_non_interactive_roles(current_role) || is_presentation_role(current_role))
850853
) {
@@ -853,6 +856,7 @@ export function check_element(node, context) {
853856

854857
// no-noninteractive-element-to-interactive-role
855858
if (
859+
!has_spread &&
856860
is_non_interactive_element(node.name, attribute_map) &&
857861
is_interactive_roles(current_role) &&
858862
!a11y_non_interactive_element_to_interactive_role_exceptions[node.name]?.includes(
@@ -947,6 +951,7 @@ export function check_element(node, context) {
947951

948952
// no-noninteractive-element-interactions
949953
if (
954+
!has_spread &&
950955
!has_contenteditable_attr &&
951956
!is_hidden_from_screen_reader(node.name, attribute_map) &&
952957
!is_presentation_role(role_static_value) &&
@@ -964,6 +969,7 @@ export function check_element(node, context) {
964969

965970
// no-static-element-interactions
966971
if (
972+
!has_spread &&
967973
(!role || role_static_value !== null) &&
968974
!is_hidden_from_screen_reader(node.name, attribute_map) &&
969975
!is_presentation_role(role_static_value) &&
@@ -981,11 +987,11 @@ export function check_element(node, context) {
981987
}
982988
}
983989

984-
if (handlers.has('mouseover') && !handlers.has('focus')) {
990+
if (!has_spread && handlers.has('mouseover') && !handlers.has('focus')) {
985991
w.a11y_mouse_events_have_key_events(node, 'mouseover', 'focus');
986992
}
987993

988-
if (handlers.has('mouseout') && !handlers.has('blur')) {
994+
if (!has_spread && handlers.has('mouseout') && !handlers.has('blur')) {
989995
w.a11y_mouse_events_have_key_events(node, 'mouseout', 'blur');
990996
}
991997

@@ -995,7 +1001,7 @@ export function check_element(node, context) {
9951001
if (node.name === 'a' || node.name === 'button') {
9961002
const is_hidden = get_static_value(attribute_map.get('aria-hidden')) === 'true';
9971003

998-
if (!is_hidden && !is_labelled && !has_content(node)) {
1004+
if (!has_spread && !is_hidden && !is_labelled && !has_content(node)) {
9991005
w.a11y_consider_explicit_label(node);
10001006
}
10011007
}
@@ -1054,7 +1060,7 @@ export function check_element(node, context) {
10541060
if (node.name === 'img') {
10551061
const alt_attribute = get_static_text_value(attribute_map.get('alt'));
10561062
const aria_hidden = get_static_value(attribute_map.get('aria-hidden'));
1057-
if (alt_attribute && !aria_hidden) {
1063+
if (alt_attribute && !aria_hidden && !has_spread) {
10581064
if (/\b(image|picture|photo)\b/i.test(alt_attribute)) {
10591065
w.a11y_img_redundant_alt(node);
10601066
}
@@ -1087,15 +1093,15 @@ export function check_element(node, context) {
10871093
);
10881094
return has;
10891095
};
1090-
if (!attribute_map.has('for') && !has_input_child(node)) {
1096+
if (!has_spread && !attribute_map.has('for') && !has_input_child(node)) {
10911097
w.a11y_label_has_associated_control(node);
10921098
}
10931099
}
10941100

10951101
if (node.name === 'video') {
10961102
const aria_hidden_attribute = attribute_map.get('aria-hidden');
10971103
const aria_hidden_exist = aria_hidden_attribute && get_static_value(aria_hidden_attribute);
1098-
if (attribute_map.has('muted') || aria_hidden_exist === 'true') {
1104+
if (attribute_map.has('muted') || aria_hidden_exist === 'true' || has_spread) {
10991105
return;
11001106
}
11011107
let has_caption = false;
@@ -1141,6 +1147,7 @@ export function check_element(node, context) {
11411147

11421148
// Check content
11431149
if (
1150+
!has_spread &&
11441151
!is_labelled &&
11451152
!has_contenteditable_binding &&
11461153
a11y_required_content.includes(node.name) &&

packages/svelte/tests/validator/samples/a11y-label-has-associated-control/input.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
<label>E <span></span></label>
1111
<label>F {#if true}<input type="text" />{/if}</label>
1212
<LabelComponent>G <input type="text" /></LabelComponent>
13+
<label {...forMightBeInHere}>E <span></span></label>
Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
<script>
2-
// Even if otherProps contains onBlur and/or onFocus, the rule will still fail.
3-
// Props should be passed down explicitly for rule to pass.
42
const otherProps = {
5-
onBlur: () => void 0,
6-
onFocus: () => void 0
3+
onblur: () => {},
4+
onfocus: () => {}
75
};
86
</script>
97

108
<!-- svelte-ignore a11y_no_static_element_interactions -->
11-
<div on:mouseover={() => void 0}></div>
9+
<div onmouseover={() => {}}></div>
1210
<!-- svelte-ignore a11y_no_static_element_interactions -->
13-
<div on:mouseover={() => void 0} on:focus={() => void 0}></div>
11+
<div onmouseover={() => {}} onfocus={() => {}}></div>
1412
<!-- svelte-ignore a11y_no_static_element_interactions -->
15-
<div on:mouseover={() => void 0} {...otherProps}></div>
13+
<div onmouseover={() => {}} {...otherProps}></div>
1614
<!-- svelte-ignore a11y_no_static_element_interactions -->
17-
<div on:mouseout={() => void 0}></div>
15+
<div onmouseout={() => {}}></div>
1816
<!-- svelte-ignore a11y_no_static_element_interactions -->
19-
<div on:mouseout={() => void 0} on:blur={() => void 0}></div>
17+
<div onmouseout={() => {}} onblur={() => {}}></div>
2018
<!-- svelte-ignore a11y_no_static_element_interactions -->
21-
<div on:mouseout={() => void 0} {...otherProps}></div>
19+
<div onmouseout={() => {}} {...otherProps}></div>

packages/svelte/tests/validator/samples/a11y-mouse-events-have-key-events/warnings.json

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,25 @@
22
{
33
"code": "a11y_mouse_events_have_key_events",
44
"end": {
5-
"column": 39,
6-
"line": 11
5+
"column": 34,
6+
"line": 9
77
},
88
"message": "'mouseover' event must be accompanied by 'focus' event",
99
"start": {
1010
"column": 0,
11-
"line": 11
11+
"line": 9
1212
}
1313
},
1414
{
1515
"code": "a11y_mouse_events_have_key_events",
1616
"end": {
17-
"column": 55,
17+
"column": 33,
1818
"line": 15
1919
},
20-
"message": "'mouseover' event must be accompanied by 'focus' event",
21-
"start": {
22-
"column": 0,
23-
"line": 15
24-
}
25-
},
26-
{
27-
"code": "a11y_mouse_events_have_key_events",
28-
"end": {
29-
"column": 38,
30-
"line": 17
31-
},
3220
"message": "'mouseout' event must be accompanied by 'blur' event",
3321
"start": {
3422
"column": 0,
35-
"line": 17
36-
}
37-
},
38-
{
39-
"code": "a11y_mouse_events_have_key_events",
40-
"end": {
41-
"column": 54,
42-
"line": 21
43-
},
44-
"message": "'mouseout' event must be accompanied by 'blur' event",
45-
"start": {
46-
"column": 0,
47-
"line": 21
23+
"line": 15
4824
}
4925
}
5026
]

0 commit comments

Comments
 (0)