From 676716979a95bf33333a2ba8185ceea63e7d7655 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 5 Dec 2022 15:00:51 +0100 Subject: [PATCH] [fix] a11y - allow fallback roles (#8045) --- src/compiler/compile/nodes/Element.ts | 77 ++++++++++--------- .../samples/a11y-aria-role/input.svelte | 8 +- .../samples/a11y-aria-role/warnings.json | 25 ++++-- 3 files changed, 68 insertions(+), 42 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index ca9bcb8c4e..a499d013e3 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -194,10 +194,10 @@ function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: st .indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1; case 'idlist': // if list of ids, split each return typeof value === 'string' - && value.split(' ').every((id) => typeof id === 'string'); + && value.split(regex_any_repeated_whitespaces).every((id) => typeof id === 'string'); case 'tokenlist': // if list of tokens, split each return typeof value === 'string' - && value.split(' ').every((token) => (schema.values || []).indexOf(token.toLowerCase()) > -1); + && value.split(regex_any_repeated_whitespaces).every((token) => (schema.values || []).indexOf(token.toLowerCase()) > -1); default: return false; } @@ -492,47 +492,52 @@ export default class Element extends Node { component.warn(attribute, compiler_warnings.a11y_misplaced_role(this.name)); } - const value = attribute.get_static_value() as ARIARoleDefintionKey; + const value = attribute.get_static_value(); - if (value && aria_role_abstract_set.has(value)) { - component.warn(attribute, compiler_warnings.a11y_no_abstract_role(value)); - } else if (value && !aria_role_set.has(value)) { - const match = fuzzymatch(value, aria_roles); - component.warn(attribute, compiler_warnings.a11y_unknown_role(value, match)); - } + if (typeof value === 'string') { + value.split(regex_any_repeated_whitespaces).forEach((current_role: ARIARoleDefintionKey) => { + if (current_role && aria_role_abstract_set.has(current_role)) { + component.warn(attribute, compiler_warnings.a11y_no_abstract_role(current_role)); + } else if (current_role && !aria_role_set.has(current_role)) { + const match = fuzzymatch(current_role, aria_roles); + component.warn(attribute, compiler_warnings.a11y_unknown_role(current_role, match)); + } - // no-redundant-roles - const has_redundant_role = value === a11y_implicit_semantics.get(this.name); + // no-redundant-roles + const has_redundant_role = current_role === a11y_implicit_semantics.get(this.name); - if (this.name === value || has_redundant_role) { - component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(value)); - } + if (this.name === current_role || has_redundant_role) { + component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role)); + } - // Footers and headers are special cases, and should not have redundant roles unless they are the children of sections or articles. - const is_parent_section_or_article = is_parent(this.parent, ['section', 'article']); - if (!is_parent_section_or_article) { - const has_nested_redundant_role = value === a11y_nested_implicit_semantics.get(this.name); - if (has_nested_redundant_role) { - component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(value)); - } - } + // Footers and headers are special cases, and should not have redundant roles unless they are the children of sections or articles. + const is_parent_section_or_article = is_parent(this.parent, ['section', 'article']); + if (!is_parent_section_or_article) { + const has_nested_redundant_role = current_role === a11y_nested_implicit_semantics.get(this.name); + if (has_nested_redundant_role) { + component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role)); + } + } - // role-has-required-aria-props - if (!is_semantic_role_element(value, this.name, attribute_map)) { - const role = roles.get(value); - 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)); + // role-has-required-aria-props + if (!is_semantic_role_element(current_role, this.name, attribute_map)) { + const role = roles.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)); - if (has_missing_props) { - component.warn(attribute, compiler_warnings.a11y_role_has_required_aria_props(value as string, required_role_props)); + if (has_missing_props) { + component.warn(attribute, compiler_warnings.a11y_role_has_required_aria_props(current_role, required_role_props)); + } + } } - } - } - // no-interactive-element-to-noninteractive-role - if (is_interactive_element(this.name, attribute_map) && (is_non_interactive_roles(value) || is_presentation_role(value))) { - component.warn(this, compiler_warnings.a11y_no_interactive_element_to_noninteractive_role(value, this.name)); + // no-interactive-element-to-noninteractive-role + if (is_interactive_element(this.name, attribute_map) && (is_non_interactive_roles(current_role) || is_presentation_role(current_role))) { + component.warn(this, compiler_warnings.a11y_no_interactive_element_to_noninteractive_role(current_role, this.name)); + } + }); + } } @@ -621,7 +626,7 @@ export default class Element extends Node { if (href_static_value === null || href_static_value.match(/^(https?:)?\/\//i)) { const rel = attribute_map.get('rel'); if (rel == null || rel.is_static) { - const rel_values = rel ? rel.get_static_value().split(' ') : []; + const rel_values = rel ? rel.get_static_value().split(regex_any_repeated_whitespaces) : []; const expected_values = ['noreferrer']; expected_values.forEach(expected_value => { if (!rel || rel && rel_values.indexOf(expected_value) < 0) { diff --git a/test/validator/samples/a11y-aria-role/input.svelte b/test/validator/samples/a11y-aria-role/input.svelte index 3089d84e5b..1dde21a2ad 100644 --- a/test/validator/samples/a11y-aria-role/input.svelte +++ b/test/validator/samples/a11y-aria-role/input.svelte @@ -1 +1,7 @@ -
\ No newline at end of file + +
+
+ + +
+
diff --git a/test/validator/samples/a11y-aria-role/warnings.json b/test/validator/samples/a11y-aria-role/warnings.json index 22fb2d52b6..2943f232f8 100644 --- a/test/validator/samples/a11y-aria-role/warnings.json +++ b/test/validator/samples/a11y-aria-role/warnings.json @@ -3,15 +3,30 @@ "code": "a11y-unknown-role", "message": "A11y: Unknown role 'toooltip' (did you mean 'tooltip'?)", "start": { - "line": 1, + "line": 6, "column": 5, - "character": 5 + "character": 101 }, "end": { - "line": 1, + "line": 6, "column": 20, - "character": 20 + "character": 116 }, - "pos": 5 + "pos": 101 + }, + { + "code": "a11y-unknown-role", + "message": "A11y: Unknown role 'toooltip' (did you mean 'tooltip'?)", + "start": { + "line": 7, + "column": 5, + "character": 129 + }, + "end": { + "line": 7, + "column": 27, + "character": 151 + }, + "pos": 129 } ]