Skip to content

Commit

Permalink
a11y: Add role-has-required-aria-props rule (#5852)
Browse files Browse the repository at this point in the history
* Check required props for ARIA roles

* Test required ARIA props check

* Properly indent with tabs in test

* swtich to use aria-query

* fix validation test

* update docs

Co-authored-by: tanhauhau <lhtan93@gmail.com>
  • Loading branch information
Melonai and tanhauhau committed Jul 10, 2022
1 parent c01dc62 commit 1d19aeb
Show file tree
Hide file tree
Showing 8 changed files with 867 additions and 633 deletions.
29 changes: 29 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -124,12 +124,14 @@
"@rollup/plugin-typescript": "^2.0.1",
"@rollup/plugin-virtual": "^2.0.0",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.8.0",
"@types/aria-query": "^5.0.0",
"@types/mocha": "^7.0.0",
"@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"acorn": "^8.4.1",
"agadoo": "^1.1.0",
"aria-query": "^5.0.0",
"code-red": "^0.2.5",
"css-tree": "^1.1.2",
"eslint": "^8.0.0",
Expand Down
11 changes: 11 additions & 0 deletions site/content/docs/05-accessibility-warnings.md
Expand Up @@ -54,6 +54,17 @@ The following elements are visually distracting: `<marquee>` and `<blink>`.

---

### `role-has-required-aria-props`

Elements with ARIA roles must have all required attributes for that role.

```sv
<!-- A11y: A11y: Elements with the ARIA role "checkbox" must have the following attributes defined: "aria-checked" -->
<span role="checkbox" aria-labelledby="foo" tabindex="0"></span>
```

---

### `a11y-hidden`

Certain DOM elements are useful for screen reader navigation and should not be hidden.
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/compile/compiler_warnings.ts
Expand Up @@ -80,6 +80,10 @@ export default {
code: 'a11y-no-redundant-roles',
message: `A11y: Redundant role '${role}'`
}),
a11y_role_has_required_aria_props: (role: string, props: string[]) => ({
code: 'a11y-role-has-required-aria-props',
message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props.map(name => `"${name}"`).join(', ')}`
}),
a11y_accesskey: {
code: 'a11y-accesskey',
message: 'A11y: Avoid using accesskey'
Expand Down
16 changes: 14 additions & 2 deletions src/compiler/compile/nodes/Element.ts
Expand Up @@ -23,6 +23,7 @@ import { string_literal } from '../utils/stringify';
import { Literal } from 'estree';
import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors';
import { ARIARoleDefintionKey, roles } from 'aria-query';

const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;

Expand Down Expand Up @@ -407,9 +408,9 @@ export default class Element extends Node {
}

validate_attributes_a11y() {
const { component } = this;
const { component, attributes } = this;

this.attributes.forEach(attribute => {
attributes.forEach(attribute => {
if (attribute.is_spread) return;

const name = attribute.name.toLowerCase();
Expand Down Expand Up @@ -462,6 +463,17 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(value));
}
}

// role-has-required-aria-props
const role = roles.get(value as ARIARoleDefintionKey);
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));
}
}
}

// no-access-key
Expand Down

0 comments on commit 1d19aeb

Please sign in to comment.