From a90e07208638466de258293abb5c6086ead9dfd1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 3 Sep 2017 17:18:48 -0400 Subject: [PATCH] alt-text --- src/generators/dom/preprocess.ts | 2 +- .../dom/visitors/Element/Attribute.ts | 2 +- .../dom/visitors/Element/Binding.ts | 2 +- .../dom/visitors/Element/Element.ts | 2 +- .../dom/visitors/Element/StyleAttribute.ts | 2 +- src/generators/dom/visitors/Slot.ts | 2 +- .../shared/getStaticAttributeValue.ts | 15 ---------- src/utils/getStaticAttributeValue.ts | 17 +++++++++++ src/validate/html/a11y.ts | 30 +++++++++++++++---- 9 files changed, 48 insertions(+), 26 deletions(-) delete mode 100644 src/generators/shared/getStaticAttributeValue.ts create mode 100644 src/utils/getStaticAttributeValue.ts diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index e602af60ae..b945c902b9 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -1,7 +1,7 @@ import Block from './Block'; import { trimStart, trimEnd } from '../../utils/trim'; import { assign } from '../../shared/index.js'; -import getStaticAttributeValue from '../shared/getStaticAttributeValue'; +import getStaticAttributeValue from '../../utils/getStaticAttributeValue'; import { DomGenerator } from './index'; import { Node } from '../../interfaces'; import { State } from './interfaces'; diff --git a/src/generators/dom/visitors/Element/Attribute.ts b/src/generators/dom/visitors/Element/Attribute.ts index a994c6b58b..837e0945db 100644 --- a/src/generators/dom/visitors/Element/Attribute.ts +++ b/src/generators/dom/visitors/Element/Attribute.ts @@ -3,7 +3,7 @@ import deindent from '../../../../utils/deindent'; import visitStyleAttribute, { optimizeStyle } from './StyleAttribute'; import { stringify } from '../../../../utils/stringify'; import getExpressionPrecedence from '../../../../utils/getExpressionPrecedence'; -import getStaticAttributeValue from '../../../shared/getStaticAttributeValue'; +import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue'; import { DomGenerator } from '../../index'; import Block from '../../Block'; import { Node } from '../../../../interfaces'; diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts index 8878af17d0..492cebd5b4 100644 --- a/src/generators/dom/visitors/Element/Binding.ts +++ b/src/generators/dom/visitors/Element/Binding.ts @@ -1,6 +1,6 @@ import deindent from '../../../../utils/deindent'; import flattenReference from '../../../../utils/flattenReference'; -import getStaticAttributeValue from '../../../shared/getStaticAttributeValue'; +import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue'; import { DomGenerator } from '../../index'; import Block from '../../Block'; import { Node } from '../../../../interfaces'; diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index f26c3e341b..114482f649 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -8,7 +8,7 @@ import visitEventHandler from './EventHandler'; import visitBinding from './Binding'; import visitRef from './Ref'; import * as namespaces from '../../../../utils/namespaces'; -import getStaticAttributeValue from '../../../shared/getStaticAttributeValue'; +import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue'; import addTransitions from './addTransitions'; import { DomGenerator } from '../../index'; import Block from '../../Block'; diff --git a/src/generators/dom/visitors/Element/StyleAttribute.ts b/src/generators/dom/visitors/Element/StyleAttribute.ts index 4cf5776145..ab661bfe06 100644 --- a/src/generators/dom/visitors/Element/StyleAttribute.ts +++ b/src/generators/dom/visitors/Element/StyleAttribute.ts @@ -2,7 +2,7 @@ import attributeLookup from './lookup'; import deindent from '../../../../utils/deindent'; import { stringify } from '../../../../utils/stringify'; import getExpressionPrecedence from '../../../../utils/getExpressionPrecedence'; -import getStaticAttributeValue from '../../../shared/getStaticAttributeValue'; +import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue'; import { DomGenerator } from '../../index'; import Block from '../../Block'; import { Node } from '../../../../interfaces'; diff --git a/src/generators/dom/visitors/Slot.ts b/src/generators/dom/visitors/Slot.ts index 5249879f40..ef3651e1bf 100644 --- a/src/generators/dom/visitors/Slot.ts +++ b/src/generators/dom/visitors/Slot.ts @@ -2,7 +2,7 @@ import { DomGenerator } from '../index'; import deindent from '../../../utils/deindent'; import visit from '../visit'; import Block from '../Block'; -import getStaticAttributeValue from '../../shared/getStaticAttributeValue'; +import getStaticAttributeValue from '../../../utils/getStaticAttributeValue'; import { Node } from '../../../interfaces'; import { State } from '../interfaces'; diff --git a/src/generators/shared/getStaticAttributeValue.ts b/src/generators/shared/getStaticAttributeValue.ts deleted file mode 100644 index 02d38343d3..0000000000 --- a/src/generators/shared/getStaticAttributeValue.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Node } from '../../interfaces'; - -export default function getStaticAttributeValue(node: Node, name: string) { - const attribute = node.attributes.find( - (attr: Node) => attr.name.toLowerCase() === name - ); - if (!attribute) return null; - - if (attribute.value.length !== 1 || attribute.value[0].type !== 'Text') { - // TODO catch this in validation phase, give a more useful error (with location etc) - throw new Error(`'${name}' must be a static attribute`); - } - - return attribute.value[0].data; -} diff --git a/src/utils/getStaticAttributeValue.ts b/src/utils/getStaticAttributeValue.ts new file mode 100644 index 0000000000..67bea12259 --- /dev/null +++ b/src/utils/getStaticAttributeValue.ts @@ -0,0 +1,17 @@ +import { Node } from '../interfaces'; + +export default function getStaticAttributeValue(node: Node, name: string) { + const attribute = node.attributes.find( + (attr: Node) => attr.name.toLowerCase() === name + ); + + if (!attribute) return null; + + if (attribute.value.length === 0) return ''; + + if (attribute.value.length === 1 && attribute.value[0].type === 'Text') { + return attribute.value[0].data; + } + + return null; +} diff --git a/src/validate/html/a11y.ts b/src/validate/html/a11y.ts index 359b575670..c7d50f17e3 100644 --- a/src/validate/html/a11y.ts +++ b/src/validate/html/a11y.ts @@ -1,4 +1,5 @@ import * as namespaces from '../../utils/namespaces'; +import getStaticAttributeValue from '../../utils/getStaticAttributeValue'; import validateEventHandler from './validateEventHandler'; import { Validator } from '../index'; import { Node } from '../../interfaces'; @@ -20,11 +21,27 @@ export default function a11y( attributeMap.set(attribute.name, attribute); }); + function shouldHaveOneOf(attributes: string[], name = node.name) { + if (attributes.length === 0 || !attributes.some((name: string) => attributeMap.has(name))) { + const article = /^[aeiou]/.test(attributes[0]) ? 'an' : 'a'; + const sequence = attributes.length > 1 ? + attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` : + attributes[0]; + + console.log(`warning about ${name}: ${sequence}`) + validator.warn(`A11y: <${name}> element should have ${article} ${sequence} attribute`, node.start); + } + + else { + console.log('ok', node.name); + } + } + if (node.name === 'a') { // anchor-is-valid const href = attributeMap.get('href'); - if (href) { - const value = getValue(href); + if (attributeMap.has('href')) { + const value = getStaticAttributeValue(node, 'href'); if (value === '' || value === '#') { validator.warn(`A11y: '${value}' is not a valid href attribute`, href.start); } @@ -33,13 +50,16 @@ export default function a11y( } // anchor-has-content - if (!node.children.length) { + if (node.children.length === 0) { 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 === 'img') shouldHaveOneOf(['alt']); + if (node.name === 'area') shouldHaveOneOf(['alt', 'aria-label', 'aria-labelledby']); + if (node.name === 'object') shouldHaveOneOf(['title', 'aria-label', 'aria-labelledby']); + if (node.name === 'input' && getStaticAttributeValue(node, 'type') === 'image') { + shouldHaveOneOf(['alt', 'aria-label', 'aria-labelledby'], 'input type="image"'); } if (node.name === 'figcaption') {