From 54ed73e5248ed6aea7872f379badec1df9182701 Mon Sep 17 00:00:00 2001 From: mhatvan Date: Sun, 6 Sep 2020 16:39:58 +0200 Subject: [PATCH] feat(a11y): add no-redundant-roles rule (#820) - flagging easy cases of redundant aria roles on html tags for now Signed-off-by: mhatvan --- src/compiler/compile/nodes/Element.ts | 75 +++++++++++++-- .../a11y-no-redundant-roles/input.svelte | 15 +++ .../a11y-no-redundant-roles/warnings.json | 92 +++++++++++++++++++ 3 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 test/validator/samples/a11y-no-redundant-roles/input.svelte create mode 100644 test/validator/samples/a11y-no-redundant-roles/warnings.json diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 6636c6b87b..b7ef3c471e 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -214,12 +214,12 @@ export default class Element extends Node { } case 'Transition': - { - const transition = new Transition(component, this, scope, node); - if (node.intro) this.intro = transition; - if (node.outro) this.outro = transition; - break; - } + { + const transition = new Transition(component, this, scope, node); + if (node.intro) this.intro = transition; + if (node.outro) this.outro = transition; + break; + } case 'Animation': this.animation = new Animation(component, this, scope, node); @@ -357,6 +357,67 @@ export default class Element extends Node { message }); } + + const implicitAriaSemantics = new Map([ + // ['a', ''], + // ['area', ''], + ['article', 'article'], + ['aside', 'complementary'], + ['body', 'document'], + ['button', 'button'], + ['datalist', 'listbox'], + ['dd', 'definition'], + ['dfn', 'term'], + ['details', 'group'], + ['dialog', 'dialog'], + ['dt', 'term'], + ['fieldset', 'group'], + ['figure', 'figure'], + // ['footer', ''], + ['form', 'form'], + ['h1', 'heading'], + ['h2', 'heading'], + ['h3', 'heading'], + ['h4', 'heading'], + ['h5', 'heading'], + ['h6', 'heading'], + // ['header', ''], + ['hr', 'separator'], + // ['img', ''], + // ['input', ''], + ['li', 'listitem'], + ['link', 'link'], + ['main', 'main'], + ['math', 'math'], + ['menu', 'list'], + ['nav', 'navigation'], + ['ol', 'list'], + ['optgroup', 'group'], + ['option', 'option'], + ['output', 'status'], + ['progress', 'progressbar'], + ['section', 'region'], + // ['select', ''], + ['summary', 'button'], + ['table', 'table'], + ['tbody', 'rowgroup'], + ['textarea', 'textbox'], + ['tfoot', 'rowgroup'], + ['thead', 'rowgroup'], + // ['td', ''], + // ['th', ''], + ['tr', 'row'], + ['ul', 'list'], + ['ul', 'list'] + ]); + + const redundantAriaRole = implicitAriaSemantics.get(this.name); + + if (value === redundantAriaRole) + component.warn(this, { + code: `a11y-no-redundant-roles`, + message: `A11y: The element <${this.name}> has an implicit role of '${redundantAriaRole}'. Defining this explicitly is redundant and should be avoided.` + }); } // no-access-key @@ -519,7 +580,7 @@ export default class Element extends Node { } if (this.name === 'label') { - const has_input_child = this.children.some(i => (i instanceof Element && a11y_labelable.has(i.name) )); + const has_input_child = this.children.some(i => (i instanceof Element && a11y_labelable.has(i.name))); if (!attribute_map.has('for') && !has_input_child) { component.warn(this, { code: `a11y-label-has-associated-control`, diff --git a/test/validator/samples/a11y-no-redundant-roles/input.svelte b/test/validator/samples/a11y-no-redundant-roles/input.svelte new file mode 100644 index 0000000000..d5a67cfeeb --- /dev/null +++ b/test/validator/samples/a11y-no-redundant-roles/input.svelte @@ -0,0 +1,15 @@ + +
+