Skip to content

Commit

Permalink
[feat] add a11y-no-redundant-roles check (#7067)
Browse files Browse the repository at this point in the history
Part of #820
Closes #5361

Co-authored-by: mhatvan <[email protected]>
  • Loading branch information
James Bradbury and mhatvan authored Jan 3, 2022
1 parent 9221f21 commit 84a4ef0
Show file tree
Hide file tree
Showing 4 changed files with 753 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/compiler/compile/compiler_warnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ export default {
code: 'a11y-unknown-role',
message: `A11y: Unknown role '${role}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '')
}),
a11y_no_redundant_roles: (role: string | boolean) => ({
code: 'a11y-no-redundant-roles',
message: `A11y: Redundant role '${role}'`
}),
a11y_accesskey: {
code: 'a11y-accesskey',
message: 'A11y: Avoid using accesskey'
Expand Down
73 changes: 73 additions & 0 deletions src/compiler/compile/nodes/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,46 @@ const a11y_labelable = new Set([
'textarea'
]);

const a11y_nested_implicit_semantics = new Map([
['header', 'banner'],
['footer', 'contentinfo']
]);

const a11y_implicit_semantics = new Map([
['a', 'link'],
['aside', 'complementary'],
['body', 'document'],
['datalist', 'listbox'],
['dd', 'definition'],
['dfn', 'term'],
['details', 'group'],
['dt', 'term'],
['fieldset', 'group'],
['form', 'form'],
['h1', 'heading'],
['h2', 'heading'],
['h3', 'heading'],
['h4', 'heading'],
['h5', 'heading'],
['h6', 'heading'],
['hr', 'separator'],
['li', 'listitem'],
['menu', 'list'],
['nav', 'navigation'],
['ol', 'list'],
['optgroup', 'group'],
['output', 'status'],
['progress', 'progressbar'],
['section', 'region'],
['summary', 'button'],
['tbody', 'rowgroup'],
['textarea', 'textbox'],
['tfoot', 'rowgroup'],
['thead', 'rowgroup'],
['tr', 'row'],
['ul', 'list']
]);

const invisible_elements = new Set(['meta', 'html', 'script', 'style']);

const valid_modifiers = new Set([
Expand Down Expand Up @@ -98,6 +138,23 @@ const react_attributes = new Map([

const attributes_to_compact_whitespace = ['class', 'style'];

function is_parent(parent: INode, elements: string[]) {
let check = false;

while (parent) {
const parent_name = (parent as Element).name;
if (elements.includes(parent_name)) {
check = true;
break;
}
if (parent.type === 'Element') {
break;
}
parent = parent.parent;
}
return check;
}

function get_namespace(parent: Element, element: Element, explicit_namespace: string) {
const parent_element = parent.find_nearest(/^Element/);

Expand Down Expand Up @@ -351,6 +408,22 @@ export default class Element extends Node {
const match = fuzzymatch(value, aria_roles);
component.warn(attribute, compiler_warnings.a11y_unknown_role(value, match));
}

// no-redundant-roles
const has_redundant_role = value === a11y_implicit_semantics.get(this.name);

if (this.name === value || has_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 = value === a11y_nested_implicit_semantics.get(this.name);
if (has_nested_redundant_role) {
component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(value));
}
}
}

// no-access-key
Expand Down
44 changes: 44 additions & 0 deletions test/validator/samples/a11y-no-redundant-roles/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<a href="/" role="link">a link</a>
<article role="article" />
<aside role="complementary" />
<body role="document" />
<button role="button" />
<datalist role="listbox" />
<dd role="definition" />
<dfn role="term" />
<details role="group" />
<dialog role="dialog" />
<dt role="term" />
<fieldset role="group" />
<figure role="figure" />
<form role="form">foo</form>
<h1 role="heading">heading</h1>
<h2 role="heading">heading</h2>
<h3 role="heading">heading</h3>
<h4 role="heading">heading</h4>
<h5 role="heading">heading</h5>
<h6 role="heading">heading</h6>
<hr role="separator" />
<li role="listitem" />
<link role="link" />
<main role="main"></main>
<menu role="list" />
<nav role="navigation" />
<ol role="list" />
<optgroup role="group" />
<option role="option" />
<output role="status" />
<progress role="progressbar" />
<section role="region" />
<summary role="button" />
<table role="table" />
<tbody role="rowgroup" />
<textarea role="textbox" />
<tfoot role="rowgroup" />
<thead role="rowgroup" />
<tr role="row" />
<ul role="list" />

<!-- Tested header/footer not nested in section/article -->
<header role="banner"></header>
<footer role="contentinfo"></footer>
Loading

0 comments on commit 84a4ef0

Please sign in to comment.