diff --git a/src/validate/html/a11y.ts b/src/validate/html/a11y.ts new file mode 100644 index 0000000000..70beea0ac3 --- /dev/null +++ b/src/validate/html/a11y.ts @@ -0,0 +1,50 @@ +import * as namespaces from '../../utils/namespaces'; +import validateEventHandler from './validateEventHandler'; +import { Validator } from '../index'; +import { Node } from '../../interfaces'; + +export default function a11y( + validator: Validator, + node: Node, + elementStack: Node[] +) { + if (node.type === 'Text') { + // accessible-emoji + return; + } + + if (node.type !== 'Element') return; + + const attributeMap = new Map(); + node.attributes.forEach((attribute: Node) => { + attributeMap.set(attribute.name, attribute); + }); + + if (node.name === 'a') { + if (!attributeMap.has('href')) { + validator.warn(`A11y: element should have an href attribute`, node.start); + } + + if (!node.children.length) { + validator.warn(`A11y: element should have child content`, node.start); + } + } + + if (node.name === 'img' && !attributeMap.has('alt')) { + validator.warn(`A11y: element should have an alt attribute`, node.start); + } + + if (node.name === 'figcaption') { + const parent = elementStack[elementStack.length - 1]; + if (parent) { + if (parent.name !== 'figure') { + validator.warn(`A11y:
must be an immediate child of
`, node.start); + } else { + const index = parent.children.indexOf(node); + if (index !== 0 && index !== parent.children.length - 1) { + validator.warn(`A11y:
must be first or last child of
`, node.start); + } + } + } + } +} \ No newline at end of file diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts index 186181e913..d36b00b833 100644 --- a/src/validate/html/index.ts +++ b/src/validate/html/index.ts @@ -1,5 +1,6 @@ import validateElement from './validateElement'; import validateWindow from './validateWindow'; +import a11y from './a11y'; import fuzzymatch from '../utils/fuzzymatch' import flattenReference from '../../utils/flattenReference'; import { Validator } from '../index'; @@ -13,6 +14,8 @@ export default function validateHtml(validator: Validator, html: Node) { const elementStack: Node[] = []; function visit(node: Node) { + a11y(validator, node, elementStack); + if (node.type === 'Element') { if (meta.has(node.name)) { return meta.get(node.name)(validator, node, refs, refCallees); diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts index aa396bac0a..c3243c6e64 100644 --- a/src/validate/html/validateElement.ts +++ b/src/validate/html/validateElement.ts @@ -60,8 +60,6 @@ export default function validateElement( let hasOutro: boolean; let hasTransition: boolean; - const attributeMap: Map = new Map(); - node.attributes.forEach((attribute: Node) => { if (attribute.type === 'Ref') { if (!refs.has(attribute.name)) refs.set(attribute.name, []); @@ -179,8 +177,6 @@ export default function validateElement( ); } } else if (attribute.type === 'Attribute') { - attributeMap.set(attribute.name, attribute); - if (attribute.name === 'value' && node.name === 'textarea') { if (node.children.length) { validator.error( @@ -198,29 +194,6 @@ export default function validateElement( } } }); - - // a11y - if (node.name === 'a' && !attributeMap.has('href')) { - validator.warn(`A11y: element should have an href attribute`, node.start); - } - - if (node.name === 'img' && !attributeMap.has('alt')) { - validator.warn(`A11y: element should have an alt attribute`, node.start); - } - - if (node.name === 'figcaption') { - const parent = elementStack[elementStack.length - 1]; - if (parent) { - if (parent.name !== 'figure') { - validator.warn(`A11y:
must be an immediate child of
`, node.start); - } else { - const index = parent.children.indexOf(node); - if (index !== 0 && index !== parent.children.length - 1) { - validator.warn(`A11y:
must be first or last child of
`, node.start); - } - } - } - } } function checkTypeAttribute(validator: Validator, node: Node) { diff --git a/test/validator/samples/a11y-anchor-has-content/input.html b/test/validator/samples/a11y-anchor-has-content/input.html new file mode 100644 index 0000000000..ca81f90ae8 --- /dev/null +++ b/test/validator/samples/a11y-anchor-has-content/input.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/validator/samples/a11y-anchor-has-content/warnings.json b/test/validator/samples/a11y-anchor-has-content/warnings.json new file mode 100644 index 0000000000..157bec1f9b --- /dev/null +++ b/test/validator/samples/a11y-anchor-has-content/warnings.json @@ -0,0 +1,8 @@ +[{ + "message": "A11y: element should have child content", + "loc": { + "line": 1, + "column": 0 + }, + "pos": 0 +}] \ No newline at end of file