heading-has-content

pull/815/head
Rich Harris 8 years ago
parent c62a74e8ae
commit 2364f6a04d

@ -11,6 +11,8 @@ const ariaAttributeSet = new Set(ariaAttributes);
const ariaRoles = 'alert alertdialog application article banner button checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search section sectionhead select separator slider spinbutton status structure tab tablist tabpanel textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' '); const ariaRoles = 'alert alertdialog application article banner button checkbox columnheader combobox command complementary composite contentinfo definition dialog directory document form grid gridcell group heading img input landmark link list listbox listitem log main marquee math menu menubar menuitem menuitemcheckbox menuitemradio navigation note option presentation progressbar radio radiogroup range region roletype row rowgroup rowheader scrollbar search section sectionhead select separator slider spinbutton status structure tab tablist tabpanel textbox timer toolbar tooltip tree treegrid treeitem widget window'.split(' ');
const ariaRoleSet = new Set(ariaRoles); const ariaRoleSet = new Set(ariaRoles);
const invisibleElements = new Set(['meta', 'html', 'script', 'style']);
export default function a11y( export default function a11y(
validator: Validator, validator: Validator,
node: Node, node: Node,
@ -27,6 +29,11 @@ export default function a11y(
node.attributes.forEach((attribute: Node) => { node.attributes.forEach((attribute: Node) => {
// aria-props // aria-props
if (attribute.name.startsWith('aria-')) { if (attribute.name.startsWith('aria-')) {
if (invisibleElements.has(node.name)) {
// aria-unsupported-elements
validator.warn(`A11y: <${node.name}> should not have aria-* attributes`, attribute.start);
}
const name = attribute.name.slice(5); const name = attribute.name.slice(5);
if (!ariaAttributeSet.has(name)) { if (!ariaAttributeSet.has(name)) {
const match = fuzzymatch(name, ariaAttributes); const match = fuzzymatch(name, ariaAttributes);
@ -39,6 +46,11 @@ export default function a11y(
// aria-role // aria-role
if (attribute.name === 'role') { if (attribute.name === 'role') {
if (invisibleElements.has(node.name)) {
// aria-unsupported-elements
validator.warn(`A11y: <${node.name}> should not have role attribute`, attribute.start);
}
const value = getStaticAttributeValue(node, 'role'); const value = getStaticAttributeValue(node, 'role');
if (value && !ariaRoleSet.has(value)) { if (value && !ariaRoleSet.has(value)) {
const match = fuzzymatch(value, ariaRoles); const match = fuzzymatch(value, ariaRoles);
@ -63,6 +75,12 @@ export default function a11y(
} }
} }
function shouldHaveContent() {
if (node.children.length === 0) {
validator.warn(`A11y: <${node.name}> element should have child content`, node.start);
}
}
if (node.name === 'a') { if (node.name === 'a') {
// anchor-is-valid // anchor-is-valid
const href = attributeMap.get('href'); const href = attributeMap.get('href');
@ -76,9 +94,7 @@ export default function a11y(
} }
// anchor-has-content // anchor-has-content
if (node.children.length === 0) { shouldHaveContent();
validator.warn(`A11y: <a> element should have child content`, node.start);
}
} }
if (node.name === 'img') shouldHaveOneOf(['alt']); if (node.name === 'img') shouldHaveOneOf(['alt']);
@ -88,6 +104,15 @@ export default function a11y(
shouldHaveOneOf(['alt', 'aria-label', 'aria-labelledby'], 'input type="image"'); shouldHaveOneOf(['alt', 'aria-label', 'aria-labelledby'], 'input type="image"');
} }
// heading-has-content
if (/^h[1-6]$/.test(node.name)) {
shouldHaveContent();
if (attributeMap.has('aria-hidden')) {
validator.warn(`A11y: <${node.name}> element should not be hidden`, attributeMap.get('aria-hidden').start);
}
}
if (node.name === 'figcaption') { if (node.name === 'figcaption') {
const parent = elementStack[elementStack.length - 1]; const parent = elementStack[elementStack.length - 1];
if (parent) { if (parent) {

@ -57,14 +57,12 @@ export default function validateJs(validator: Validator, js: Node) {
const match = fuzzymatch(prop.key.name, validPropList); const match = fuzzymatch(prop.key.name, validPropList);
if (match) { if (match) {
validator.error( validator.error(
`Unexpected property '${prop.key `Unexpected property '${prop.key.name}' (did you mean '${match}'?)`,
.name}' (did you mean '${match}'?)`,
prop.start prop.start
); );
} else if (/FunctionExpression/.test(prop.value.type)) { } else if (/FunctionExpression/.test(prop.value.type)) {
validator.error( validator.error(
`Unexpected property '${prop.key `Unexpected property '${prop.key.name}' (did you mean to include it in 'methods'?)`,
.name}' (did you mean to include it in 'methods'?)`,
prop.start prop.start
); );
} else { } else {

@ -0,0 +1,2 @@
<meta aria-hidden="false">
<meta role="tooltip">

@ -0,0 +1,19 @@
[
{
"message": "A11y: <meta> should not have aria-* attributes",
"loc": {
"line": 1,
"column": 6
},
"pos": 6
},
{
"message": "A11y: <meta> should not have role attribute",
"loc": {
"line": 2,
"column": 6
},
"pos": 33
}
]

@ -0,0 +1,2 @@
<h1></h1>
<h2 aria-hidden>invisible header</h2>

@ -0,0 +1,19 @@
[
{
"message": "A11y: <h1> element should have child content",
"loc": {
"line": 1,
"column": 0
},
"pos": 0
},
{
"message": "A11y: <h2> element should not be hidden",
"loc": {
"line": 2,
"column": 4
},
"pos": 14
}
]
Loading…
Cancel
Save