refactor to match the eslint-plugin-jsx-a11y implementation

pull/5955/head
tanhauhau 4 years ago
parent bde3236071
commit 8e3b70f915

16
package-lock.json generated

@ -26,6 +26,7 @@
"acorn": "^8.4.1", "acorn": "^8.4.1",
"agadoo": "^1.1.0", "agadoo": "^1.1.0",
"aria-query": "^5.0.0", "aria-query": "^5.0.0",
"axobject-query": "^3.0.1",
"code-red": "^0.2.5", "code-red": "^0.2.5",
"css-tree": "^1.1.2", "css-tree": "^1.1.2",
"eslint": "^8.0.0", "eslint": "^8.0.0",
@ -812,6 +813,15 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true "dev": true
}, },
"node_modules/axobject-query": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.0.1.tgz",
"integrity": "sha512-vy5JPSOibF9yAeC2PoemRdA1MuSXX7vX5osdoxKf/6OUeppAWekZ3JIJVNWFMH6wgj7uHYyqZUSqE/b/3JLV1A==",
"dev": true,
"engines": {
"node": ">=6.0"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -5313,6 +5323,12 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true "dev": true
}, },
"axobject-query": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.0.1.tgz",
"integrity": "sha512-vy5JPSOibF9yAeC2PoemRdA1MuSXX7vX5osdoxKf/6OUeppAWekZ3JIJVNWFMH6wgj7uHYyqZUSqE/b/3JLV1A==",
"dev": true
},
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",

@ -132,6 +132,7 @@
"acorn": "^8.4.1", "acorn": "^8.4.1",
"agadoo": "^1.1.0", "agadoo": "^1.1.0",
"aria-query": "^5.0.0", "aria-query": "^5.0.0",
"axobject-query": "^3.0.1",
"code-red": "^0.2.5", "code-red": "^0.2.5",
"css-tree": "^1.1.2", "css-tree": "^1.1.2",
"eslint": "^8.0.0", "eslint": "^8.0.0",

@ -250,6 +250,17 @@ Some HTML elements have default ARIA roles. Giving these elements an ARIA role t
--- ---
### `a11y-no-interactive-element-to-noninteractive-role`
[WAI-ARIA](https://www.w3.org/TR/wai-aria-1.1/#usage_intro) roles should not be used to convert an interactive element to a non-interactive element. Non-interactive ARIA roles include `article`, `banner`, `complementary`, `img`, `listitem`, `main`, `region` and `tooltip`.
```sv
<!-- A11y: <textarea> cannot have role 'listitem' -->
<textarea role="listitem" />
```
---
### `a11y-positive-tabindex` ### `a11y-positive-tabindex`
Avoid positive `tabindex` property values. This will move elements out of the expected tab order, creating a confusing experience for keyboard users. Avoid positive `tabindex` property values. This will move elements out of the expected tab order, creating a confusing experience for keyboard users.

@ -115,6 +115,10 @@ export default {
code: 'a11y-no-redundant-roles', code: 'a11y-no-redundant-roles',
message: `A11y: Redundant role '${role}'` message: `A11y: Redundant role '${role}'`
}), }),
a11y_no_interactive_element_to_noninteractive_role: (role: string | boolean, element: string) => ({
code: 'a11y-no-interactive-element-to-noninteractive-role',
message: `A11y: <${element}> cannot have role '${role}'`
}),
a11y_role_has_required_aria_props: (role: string, props: string[]) => ({ a11y_role_has_required_aria_props: (role: string, props: string[]) => ({
code: 'a11y-role-has-required-aria-props', 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(', ')}` message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props.map(name => `"${name}"`).join(', ')}`

@ -24,15 +24,14 @@ import { Literal } from 'estree';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query'; import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query';
import { noninteractive_roles } from '../utils/aria_roles'; import { is_interactive_element, is_non_interactive_roles, is_presentation_role } from '../utils/a11y';
import { interactive_elements } from '../utils/elements';
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)$/; 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)$/;
const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
const aria_attribute_set = new Set(aria_attributes); const aria_attribute_set = new Set(aria_attributes);
const aria_roles = 'alert alertdialog application article banner blockquote button caption cell checkbox code columnheader combobox complementary contentinfo definition deletion dialog directory document emphasis feed figure form generic graphics-document graphics-object graphics-symbol grid gridcell group heading img link list listbox listitem log main marquee math meter menu menubar menuitem menuitemcheckbox menuitemradio navigation none note option paragraph presentation progressbar radio radiogroup region row rowgroup rowheader scrollbar search searchbox separator slider spinbutton status strong subscript superscript switch tab table tablist tabpanel term textbox time timer toolbar tooltip tree treegrid treeitem'.split(' '); const aria_roles = roles.keys();
const aria_role_set = new Set(aria_roles); const aria_role_set = new Set(aria_roles);
const aria_role_abstract_set = new Set(roles.keys().filter(role => roles.get(role).abstract)); const aria_role_abstract_set = new Set(roles.keys().filter(role => roles.get(role).abstract));
@ -439,6 +438,11 @@ export default class Element extends Node {
validate_attributes_a11y() { validate_attributes_a11y() {
const { component, attributes } = this; const { component, attributes } = this;
const attribute_map = new Map<string, Attribute>();
attributes.forEach(attribute => (
attribute_map.set(attribute.name, attribute)
));
attributes.forEach(attribute => { attributes.forEach(attribute => {
if (attribute.is_spread) return; if (attribute.is_spread) return;
@ -481,12 +485,11 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_misplaced_role(this.name)); component.warn(attribute, compiler_warnings.a11y_misplaced_role(this.name));
} }
const value = attribute.get_static_value(); const value = attribute.get_static_value() as ARIARoleDefintionKey;
if (value && aria_role_abstract_set.has(value as ARIARoleDefintionKey)) { if (value && aria_role_abstract_set.has(value)) {
component.warn(attribute, compiler_warnings.a11y_no_abstract_role(value)); component.warn(attribute, compiler_warnings.a11y_no_abstract_role(value));
} else if (value && !aria_role_set.has(value as string)) { } else if (value && !aria_role_set.has(value)) {
// @ts-ignore
const match = fuzzymatch(value, aria_roles); const match = fuzzymatch(value, aria_roles);
component.warn(attribute, compiler_warnings.a11y_unknown_role(value, match)); component.warn(attribute, compiler_warnings.a11y_unknown_role(value, match));
} }
@ -508,7 +511,7 @@ export default class Element extends Node {
} }
// role-has-required-aria-props // role-has-required-aria-props
const role = roles.get(value as ARIARoleDefintionKey); const role = roles.get(value);
if (role) { if (role) {
const required_role_props = Object.keys(role.requiredProps); const required_role_props = Object.keys(role.requiredProps);
const has_missing_props = required_role_props.some(prop => !attributes.find(a => a.name === prop)); const has_missing_props = required_role_props.some(prop => !attributes.find(a => a.name === prop));
@ -517,6 +520,11 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_role_has_required_aria_props(value as string, required_role_props)); component.warn(attribute, compiler_warnings.a11y_role_has_required_aria_props(value as string, 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-access-key // no-access-key
@ -686,18 +694,6 @@ export default class Element extends Node {
if (handlers_map.has('mouseout') && !handlers_map.has('blur')) { if (handlers_map.has('mouseout') && !handlers_map.has('blur')) {
component.warn(this, compiler_warnings.a11y_mouse_events_have_key_events('mouseout', 'blur')); component.warn(this, compiler_warnings.a11y_mouse_events_have_key_events('mouseout', 'blur'));
} }
if (interactive_elements.has(this.name)) {
if (attribute_map.has('role')) {
const roleValue = this.attributes.find(a => a.name === 'role').get_static_value().toString() as ARIARoleDefintionKey;
if (noninteractive_roles.has(roleValue)) {
component.warn(this, {
code: 'a11y-no-interactive-element-to-noninteractive-role',
message: `A11y: <${this.name}> cannot have role ${roleValue}`
});
}
}
}
} }
validate_bindings_foreign() { validate_bindings_foreign() {

@ -0,0 +1,137 @@
import {
ARIARoleDefintionKey,
roles as roles_map,
elementRoles,
ARIARoleRelationConcept
} from 'aria-query';
import { AXObjects, elementAXObjects } from 'axobject-query';
import Attribute from '../nodes/Attribute';
const roles = [...roles_map.keys()];
const non_interactive_roles = new Set(
roles
.filter((name) => {
const role = roles_map.get(name);
return (
!roles_map.get(name).abstract &&
// 'toolbar' does not descend from widget, but it does support
// aria-activedescendant, thus in practice we treat it as a widget.
name !== 'toolbar' &&
!role.superClass.some((classes) => classes.includes('widget'))
);
})
.concat(
// The `progressbar` is descended from `widget`, but in practice, its
// value is always `readonly`, so we treat it as a non-interactive role.
'progressbar'
)
);
const interactive_roles = new Set(
roles
.filter((name) => {
const role = roles_map.get(name);
return (
!role.abstract &&
// The `progressbar` is descended from `widget`, but in practice, its
// value is always `readonly`, so we treat it as a non-interactive role.
name !== 'progressbar' &&
role.superClass.some((classes) => classes.includes('widget'))
);
})
.concat(
// 'toolbar' does not descend from widget, but it does support
// aria-activedescendant, thus in practice we treat it as a widget.
'toolbar'
)
);
export function is_non_interactive_roles(role: ARIARoleDefintionKey) {
return non_interactive_roles.has(role);
}
const presentation_roles = new Set(['presentation', 'none']);
export function is_presentation_role(role: ARIARoleDefintionKey) {
return presentation_roles.has(role);
}
const non_interactive_element_role_schemas: ARIARoleRelationConcept[] = [];
elementRoles.entries().forEach(([schema, roles]) => {
if ([...roles].every((role) => non_interactive_roles.has(role))) {
non_interactive_element_role_schemas.push(schema);
}
});
const interactive_element_role_schemas: ARIARoleRelationConcept[] = [];
elementRoles.entries().forEach(([schema, roles]) => {
if ([...roles].every((role) => interactive_roles.has(role))) {
interactive_element_role_schemas.push(schema);
}
});
const interactive_ax_objects = new Set(
[...AXObjects.keys()].filter((name) => AXObjects.get(name).type === 'widget')
);
const interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = [];
elementAXObjects.entries().forEach(([schema, ax_object]) => {
if ([...ax_object].every((role) => interactive_ax_objects.has(role))) {
interactive_element_ax_object_schemas.push(schema);
}
});
function match_schema(
schema: ARIARoleRelationConcept,
tag_name: string,
attribute_map: Map<string, Attribute>
) {
if (schema.name !== tag_name) return false;
if (!schema.attributes) return true;
return schema.attributes.every((schema_attribute) => {
const attribute = attribute_map.get(schema_attribute.name);
if (!attribute) return false;
if (
schema_attribute.value &&
schema_attribute.value !== attribute.get_static_value()
) {
return false;
}
return true;
});
}
export function is_interactive_element(
tag_name: string,
attribute_map: Map<string, Attribute>
): boolean {
if (
interactive_element_role_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return true;
}
if (
non_interactive_element_role_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return false;
}
if (
interactive_element_ax_object_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return true;
}
return false;
}

@ -1,9 +0,0 @@
import { roles as rolesMap } from 'aria-query';
const roles = [...rolesMap.keys()];
const noninteractive_roles = new Set(roles
.filter((name) => !rolesMap.get(name).abstract)
.filter((name) => !rolesMap.get(name).superClass.some((c) => c.includes('widget'))));
export { noninteractive_roles };

@ -1,5 +0,0 @@
const interactive_elements = new Set([
'a', 'button', 'input', 'select', 'textarea'
]);
export { interactive_elements };

@ -52,3 +52,99 @@
<textarea role="region"/> <textarea role="region"/>
<textarea role="tooltip"/> <textarea role="tooltip"/>
<textarea role="button"/> <textarea role="button"/>
<!-- HTML elements attributed with an abstract role -->
<div role="command" />
<div role="composite" />
<div role="input" />
<div role="landmark" />
<div role="range" />
<div role="roletype" />
<div role="section" />
<div role="sectionhead" />
<div role="select" />
<div role="structure" />
<div role="tablist" />
<div role="toolbar" />
<div role="tree" />
<div role="treegrid" />
<div role="widget" />
<div role="window" />
<!-- HTML elements with an inherent, non-interactive role, assigned an interactive role. -->
<main role="button" />
<area role="button" alt="x" />
<article role="button" />
<article role="button" />
<dd role="button" />
<dfn role="button" />
<dt role="button" />
<fieldset role="button" />
<figure role="button" />
<form role="button" />
<frame role="button" />
<h1 role="button">title</h1>
<h2 role="button">title</h2>
<h3 role="button">title</h3>
<h4 role="button">title</h4>
<h5 role="button">title</h5>
<h6 role="button">title</h6>
<hr role="button" />
<img role="button" alt="x" />
<li role="button" />
<li role="presentation" />
<nav role="button" />
<ol role="button" />
<table role="button" />
<tbody role="button" />
<td role="button" />
<tfoot role="button" />
<thead role="button" />
<ul role="button" />
<!-- HTML elements attributed with a non-interactive role -->
<div role="alert" />
<div role="alertdialog" />
<div role="application" />
<div role="article" />
<div role="banner" />
<div role="cell" />
<div role="complementary" />
<div role="contentinfo" />
<div role="definition" />
<div role="dialog" />
<div role="directory" />
<div role="document" />
<div role="feed" />
<div role="figure" />
<div role="form" />
<div role="group" />
<div role="heading" aria-level="1" />
<div role="img" />
<div role="list" />
<div role="listitem" />
<div role="log" />
<div role="main" />
<div role="marquee" />
<div role="math" />
<div role="navigation" />
<div role="note" />
<div role="region" />
<div role="rowgroup" />
<div role="search" />
<div role="separator" />
<div role="scrollbar" aria-controls="x" aria-valuenow="0" />
<div role="status" />
<div role="table" />
<div role="tabpanel" />
<div role="term" />
<div role="timer" />
<div role="tooltip" />
<!-- not valid -->
<menuitem role="listitem" />
<option class="foo" role="listitem" />
<select class="foo" role="listitem" />
<summary role="listitem" />
<textarea class="foo" role="listitem" />
<tr role="listitem" />

@ -6,7 +6,7 @@
"column": 38, "column": 38,
"line": 2 "line": 2
}, },
"message": "A11y: <a> cannot have role article", "message": "A11y: <a> cannot have role 'article'",
"pos": 11, "pos": 11,
"start": { "start": {
"character": 11, "character": 11,
@ -21,7 +21,7 @@
"column": 37, "column": 37,
"line": 3 "line": 3
}, },
"message": "A11y: <a> cannot have role banner", "message": "A11y: <a> cannot have role 'banner'",
"pos": 50, "pos": 50,
"start": { "start": {
"character": 50, "character": 50,
@ -36,7 +36,7 @@
"column": 44, "column": 44,
"line": 4 "line": 4
}, },
"message": "A11y: <a> cannot have role complementary", "message": "A11y: <a> cannot have role 'complementary'",
"pos": 88, "pos": 88,
"start": { "start": {
"character": 88, "character": 88,
@ -51,7 +51,7 @@
"column": 34, "column": 34,
"line": 5 "line": 5
}, },
"message": "A11y: <a> cannot have role img", "message": "A11y: <a> cannot have role 'img'",
"pos": 133, "pos": 133,
"start": { "start": {
"character": 133, "character": 133,
@ -66,7 +66,7 @@
"column": 39, "column": 39,
"line": 6 "line": 6
}, },
"message": "A11y: <a> cannot have role listitem", "message": "A11y: <a> cannot have role 'listitem'",
"pos": 168, "pos": 168,
"start": { "start": {
"character": 168, "character": 168,
@ -81,7 +81,7 @@
"column": 35, "column": 35,
"line": 7 "line": 7
}, },
"message": "A11y: <a> cannot have role main", "message": "A11y: <a> cannot have role 'main'",
"pos": 208, "pos": 208,
"start": { "start": {
"character": 208, "character": 208,
@ -96,7 +96,7 @@
"column": 37, "column": 37,
"line": 8 "line": 8
}, },
"message": "A11y: <a> cannot have role region", "message": "A11y: <a> cannot have role 'region'",
"pos": 244, "pos": 244,
"start": { "start": {
"character": 244, "character": 244,
@ -111,7 +111,7 @@
"column": 38, "column": 38,
"line": 9 "line": 9
}, },
"message": "A11y: <a> cannot have role tooltip", "message": "A11y: <a> cannot have role 'tooltip'",
"pos": 282, "pos": 282,
"start": { "start": {
"character": 282, "character": 282,
@ -126,7 +126,7 @@
"column": 38, "column": 38,
"line": 13 "line": 13
}, },
"message": "A11y: <button> cannot have role article", "message": "A11y: <button> cannot have role 'article'",
"pos": 376, "pos": 376,
"start": { "start": {
"character": 376, "character": 376,
@ -141,7 +141,7 @@
"column": 37, "column": 37,
"line": 14 "line": 14
}, },
"message": "A11y: <button> cannot have role banner", "message": "A11y: <button> cannot have role 'banner'",
"pos": 415, "pos": 415,
"start": { "start": {
"character": 415, "character": 415,
@ -156,7 +156,7 @@
"column": 44, "column": 44,
"line": 15 "line": 15
}, },
"message": "A11y: <button> cannot have role complementary", "message": "A11y: <button> cannot have role 'complementary'",
"pos": 453, "pos": 453,
"start": { "start": {
"character": 453, "character": 453,
@ -171,7 +171,7 @@
"column": 34, "column": 34,
"line": 16 "line": 16
}, },
"message": "A11y: <button> cannot have role img", "message": "A11y: <button> cannot have role 'img'",
"pos": 498, "pos": 498,
"start": { "start": {
"character": 498, "character": 498,
@ -186,7 +186,7 @@
"column": 39, "column": 39,
"line": 17 "line": 17
}, },
"message": "A11y: <button> cannot have role listitem", "message": "A11y: <button> cannot have role 'listitem'",
"pos": 533, "pos": 533,
"start": { "start": {
"character": 533, "character": 533,
@ -201,7 +201,7 @@
"column": 35, "column": 35,
"line": 18 "line": 18
}, },
"message": "A11y: <button> cannot have role main", "message": "A11y: <button> cannot have role 'main'",
"pos": 573, "pos": 573,
"start": { "start": {
"character": 573, "character": 573,
@ -216,7 +216,7 @@
"column": 37, "column": 37,
"line": 19 "line": 19
}, },
"message": "A11y: <button> cannot have role region", "message": "A11y: <button> cannot have role 'region'",
"pos": 609, "pos": 609,
"start": { "start": {
"character": 609, "character": 609,
@ -231,7 +231,7 @@
"column": 38, "column": 38,
"line": 20 "line": 20
}, },
"message": "A11y: <button> cannot have role tooltip", "message": "A11y: <button> cannot have role 'tooltip'",
"pos": 647, "pos": 647,
"start": { "start": {
"character": 647, "character": 647,
@ -239,6 +239,21 @@
"line": 20 "line": 20
} }
}, },
{
"code": "a11y-no-redundant-roles",
"end": {
"character": 707,
"column": 21,
"line": 21
},
"message": "A11y: Redundant role 'button'",
"pos": 694,
"start": {
"character": 694,
"column": 8,
"line": 21
}
},
{ {
"code": "a11y-no-interactive-element-to-noninteractive-role", "code": "a11y-no-interactive-element-to-noninteractive-role",
"end": { "end": {
@ -246,7 +261,7 @@
"column": 23, "column": 23,
"line": 24 "line": 24
}, },
"message": "A11y: <input> cannot have role article", "message": "A11y: <input> cannot have role 'article'",
"pos": 740, "pos": 740,
"start": { "start": {
"character": 740, "character": 740,
@ -261,7 +276,7 @@
"column": 22, "column": 22,
"line": 25 "line": 25
}, },
"message": "A11y: <input> cannot have role banner", "message": "A11y: <input> cannot have role 'banner'",
"pos": 764, "pos": 764,
"start": { "start": {
"character": 764, "character": 764,
@ -276,7 +291,7 @@
"column": 29, "column": 29,
"line": 26 "line": 26
}, },
"message": "A11y: <input> cannot have role complementary", "message": "A11y: <input> cannot have role 'complementary'",
"pos": 787, "pos": 787,
"start": { "start": {
"character": 787, "character": 787,
@ -291,7 +306,7 @@
"column": 19, "column": 19,
"line": 27 "line": 27
}, },
"message": "A11y: <input> cannot have role img", "message": "A11y: <input> cannot have role 'img'",
"pos": 817, "pos": 817,
"start": { "start": {
"character": 817, "character": 817,
@ -306,7 +321,7 @@
"column": 24, "column": 24,
"line": 28 "line": 28
}, },
"message": "A11y: <input> cannot have role listitem", "message": "A11y: <input> cannot have role 'listitem'",
"pos": 837, "pos": 837,
"start": { "start": {
"character": 837, "character": 837,
@ -321,7 +336,7 @@
"column": 20, "column": 20,
"line": 29 "line": 29
}, },
"message": "A11y: <input> cannot have role main", "message": "A11y: <input> cannot have role 'main'",
"pos": 862, "pos": 862,
"start": { "start": {
"character": 862, "character": 862,
@ -336,7 +351,7 @@
"column": 22, "column": 22,
"line": 30 "line": 30
}, },
"message": "A11y: <input> cannot have role region", "message": "A11y: <input> cannot have role 'region'",
"pos": 883, "pos": 883,
"start": { "start": {
"character": 883, "character": 883,
@ -351,7 +366,7 @@
"column": 23, "column": 23,
"line": 31 "line": 31
}, },
"message": "A11y: <input> cannot have role tooltip", "message": "A11y: <input> cannot have role 'tooltip'",
"pos": 906, "pos": 906,
"start": { "start": {
"character": 906, "character": 906,
@ -366,7 +381,7 @@
"column": 24, "column": 24,
"line": 35 "line": 35
}, },
"message": "A11y: <select> cannot have role article", "message": "A11y: <select> cannot have role 'article'",
"pos": 970, "pos": 970,
"start": { "start": {
"character": 970, "character": 970,
@ -381,7 +396,7 @@
"column": 23, "column": 23,
"line": 36 "line": 36
}, },
"message": "A11y: <select> cannot have role banner", "message": "A11y: <select> cannot have role 'banner'",
"pos": 995, "pos": 995,
"start": { "start": {
"character": 995, "character": 995,
@ -396,7 +411,7 @@
"column": 30, "column": 30,
"line": 37 "line": 37
}, },
"message": "A11y: <select> cannot have role complementary", "message": "A11y: <select> cannot have role 'complementary'",
"pos": 1019, "pos": 1019,
"start": { "start": {
"character": 1019, "character": 1019,
@ -411,7 +426,7 @@
"column": 20, "column": 20,
"line": 38 "line": 38
}, },
"message": "A11y: <select> cannot have role img", "message": "A11y: <select> cannot have role 'img'",
"pos": 1050, "pos": 1050,
"start": { "start": {
"character": 1050, "character": 1050,
@ -426,7 +441,7 @@
"column": 25, "column": 25,
"line": 39 "line": 39
}, },
"message": "A11y: <select> cannot have role listitem", "message": "A11y: <select> cannot have role 'listitem'",
"pos": 1071, "pos": 1071,
"start": { "start": {
"character": 1071, "character": 1071,
@ -441,7 +456,7 @@
"column": 21, "column": 21,
"line": 40 "line": 40
}, },
"message": "A11y: <select> cannot have role main", "message": "A11y: <select> cannot have role 'main'",
"pos": 1097, "pos": 1097,
"start": { "start": {
"character": 1097, "character": 1097,
@ -456,7 +471,7 @@
"column": 23, "column": 23,
"line": 41 "line": 41
}, },
"message": "A11y: <select> cannot have role region", "message": "A11y: <select> cannot have role 'region'",
"pos": 1119, "pos": 1119,
"start": { "start": {
"character": 1119, "character": 1119,
@ -471,7 +486,7 @@
"column": 24, "column": 24,
"line": 42 "line": 42
}, },
"message": "A11y: <select> cannot have role tooltip", "message": "A11y: <select> cannot have role 'tooltip'",
"pos": 1143, "pos": 1143,
"start": { "start": {
"character": 1143, "character": 1143,
@ -486,7 +501,7 @@
"column": 26, "column": 26,
"line": 46 "line": 46
}, },
"message": "A11y: <textarea> cannot have role article", "message": "A11y: <textarea> cannot have role 'article'",
"pos": 1211, "pos": 1211,
"start": { "start": {
"character": 1211, "character": 1211,
@ -501,7 +516,7 @@
"column": 25, "column": 25,
"line": 47 "line": 47
}, },
"message": "A11y: <textarea> cannot have role banner", "message": "A11y: <textarea> cannot have role 'banner'",
"pos": 1238, "pos": 1238,
"start": { "start": {
"character": 1238, "character": 1238,
@ -516,7 +531,7 @@
"column": 32, "column": 32,
"line": 48 "line": 48
}, },
"message": "A11y: <textarea> cannot have role complementary", "message": "A11y: <textarea> cannot have role 'complementary'",
"pos": 1264, "pos": 1264,
"start": { "start": {
"character": 1264, "character": 1264,
@ -531,7 +546,7 @@
"column": 22, "column": 22,
"line": 49 "line": 49
}, },
"message": "A11y: <textarea> cannot have role img", "message": "A11y: <textarea> cannot have role 'img'",
"pos": 1297, "pos": 1297,
"start": { "start": {
"character": 1297, "character": 1297,
@ -546,7 +561,7 @@
"column": 27, "column": 27,
"line": 50 "line": 50
}, },
"message": "A11y: <textarea> cannot have role listitem", "message": "A11y: <textarea> cannot have role 'listitem'",
"pos": 1320, "pos": 1320,
"start": { "start": {
"character": 1320, "character": 1320,
@ -561,7 +576,7 @@
"column": 23, "column": 23,
"line": 51 "line": 51
}, },
"message": "A11y: <textarea> cannot have role main", "message": "A11y: <textarea> cannot have role 'main'",
"pos": 1348, "pos": 1348,
"start": { "start": {
"character": 1348, "character": 1348,
@ -576,7 +591,7 @@
"column": 25, "column": 25,
"line": 52 "line": 52
}, },
"message": "A11y: <textarea> cannot have role region", "message": "A11y: <textarea> cannot have role 'region'",
"pos": 1372, "pos": 1372,
"start": { "start": {
"character": 1372, "character": 1372,
@ -591,12 +606,102 @@
"column": 26, "column": 26,
"line": 53 "line": 53
}, },
"message": "A11y: <textarea> cannot have role tooltip", "message": "A11y: <textarea> cannot have role 'tooltip'",
"pos": 1398, "pos": 1398,
"start": { "start": {
"character": 1398, "character": 1398,
"column": 0, "column": 0,
"line": 53 "line": 53
} }
},
{
"code": "a11y-no-interactive-element-to-noninteractive-role",
"end": {
"character": 3695,
"column": 28,
"line": 145
},
"message": "A11y: <menuitem> cannot have role 'listitem'",
"pos": 3667,
"start": {
"character": 3667,
"column": 0,
"line": 145
}
},
{
"code": "a11y-no-interactive-element-to-noninteractive-role",
"end": {
"character": 3734,
"column": 38,
"line": 146
},
"message": "A11y: <option> cannot have role 'listitem'",
"pos": 3696,
"start": {
"character": 3696,
"column": 0,
"line": 146
}
},
{
"code": "a11y-no-interactive-element-to-noninteractive-role",
"end": {
"character": 3773,
"column": 38,
"line": 147
},
"message": "A11y: <select> cannot have role 'listitem'",
"pos": 3735,
"start": {
"character": 3735,
"column": 0,
"line": 147
}
},
{
"code": "a11y-no-interactive-element-to-noninteractive-role",
"end": {
"character": 3801,
"column": 27,
"line": 148
},
"message": "A11y: <summary> cannot have role 'listitem'",
"pos": 3774,
"start": {
"character": 3774,
"column": 0,
"line": 148
}
},
{
"code": "a11y-no-interactive-element-to-noninteractive-role",
"end": {
"character": 3842,
"column": 40,
"line": 149
},
"message": "A11y: <textarea> cannot have role 'listitem'",
"pos": 3802,
"start": {
"character": 3802,
"column": 0,
"line": 149
}
},
{
"code": "a11y-no-interactive-element-to-noninteractive-role",
"end": {
"character": 3865,
"column": 22,
"line": 150
},
"message": "A11y: <tr> cannot have role 'listitem'",
"pos": 3843,
"start": {
"character": 3843,
"column": 0,
"line": 150
}
} }
] ]
Loading…
Cancel
Save