diff --git a/site/content/docs/05-accessibility-warnings.md b/site/content/docs/05-accessibility-warnings.md index 950318055b4..438f2e47437 100644 --- a/site/content/docs/05-accessibility-warnings.md +++ b/site/content/docs/05-accessibility-warnings.md @@ -101,10 +101,10 @@ Enforce img alt attribute does not contain the word image, picture, or photo. Sc ### `a11y-incorrect-aria-attribute-type` Enforce that only the correct type of value is used for aria attributes. For example, `aria-hidden` -should only receive a boolean or `undefined`. +should only receive a boolean. ```sv - +
``` diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts index db7714d9f60..267a97afcc3 100644 --- a/src/compiler/compile/compiler_warnings.ts +++ b/src/compiler/compile/compiler_warnings.ts @@ -1,5 +1,7 @@ // All compiler warnings should be listed and accessed from here +import { ARIAPropertyDefinition } from 'aria-query'; + /** * @internal */ @@ -60,15 +62,12 @@ export default { code: 'a11y-aria-attributes', message: `A11y: <${name}> should not have aria-* attributes` }), - a11y_incorrect_attribute_type: (schema: any, attribute: string) => { + a11y_incorrect_attribute_type: (schema: ARIAPropertyDefinition, attribute: string) => { let message; switch (schema.type) { case 'boolean': message = `The value of '${attribute}' must be exactly one of true or false`; break; - case 'boolean_or_undefined': - message = `The value of '${attribute}' must be exactly one of true, false, or undefined`; - break; case 'id': message = `The value of '${attribute}' must be a string that represents a DOM element ID`; break; @@ -79,10 +78,10 @@ export default { message = `The value of '${attribute}' must be exactly one of true, false, or mixed`; break; case 'token': - message = `The value of '${attribute}' must be exactly one of ${schema.whitelist.join(', ')}`; + message = `The value of '${attribute}' must be exactly one of ${(schema.values || []).join(', ')}`; break; case 'tokenlist': - message = `The value of '${attribute}' must be a space-separated list of one or more of ${schema.whitelist.join(', ')}`; + message = `The value of '${attribute}' must be a space-separated list of one or more of ${(schema.values || []).join(', ')}`; break; default: message = `The value of '${attribute}' must be of type ${schema.type}`; diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 41e5514ffcb..b7b2572986f 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -23,7 +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'; +import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } 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)$/; @@ -46,85 +46,6 @@ const a11y_required_attributes = { object: ['title', 'aria-label', 'aria-labelledby'] }; -// Full list of attributes from https://www.w3.org/TR/wai-aria -const a11y_attribute_types = new Map([ - ['activedescendant', { type: 'id'}], - ['atomic', { type: 'boolean' }], - ['autocomplete', { - type: 'token', - whitelist: 'inline list both none'.split(' ') - }], - ['busy', { type: 'boolean'}], - ['checked', { type: 'tristate' }], - ['colcount', { type: 'integer'}], - ['colindex', { type: 'integer'}], - ['colspan', { type: 'integer'}], - ['controls', { type: 'idlist'}], - ['current', { - type: 'token', - whitelist: 'page step location date time true false'.split(' ') - }], - ['details', { type: 'id'}], - ['describedby', { type: 'idlist'}], - ['disabled', { type: 'boolean'}], - ['dropeffect', { - type: 'tokenlist', - whitelist: 'copy execute link move none popup'.split(' ') - }], - ['errormessage', { type: 'id'}], - ['expanded', { type: 'boolean_or_undefined' }], - ['flowto', { type: 'idlist'}], - ['grabbed', { type: 'boolean_or_undefined'}], - ['haspopup', { - type: 'token', - whitelist: 'false true menu listbox tree grid dialog'.split(' ') - }], - ['hidden', { type: 'boolean_or_undefined' }], - ['invalid', { - type: 'token', - whitelist: 'grammar false spelling true'.split(' ') - }], - ['keyshortcuts', { type: 'string' }], - ['label', { type: 'string' }], - ['labelledby', { type: 'idlist' }], - ['level', { type: 'integer' }], - ['live', { - type: 'token', - whitelist: 'assertive off polite'.split(' ') - }], - ['modal', { type: 'boolean' }], - ['multiline', { type: 'boolean' }], - ['multiselectable', { type: 'boolean' }], - ['orientation', { - type: 'token', - whitelist: 'vertical undefined horizontal'.split(' ') - }], - ['owns', { type: 'idlist' }], - ['placeholder', { type: 'string' }], - ['posinset', { type: 'integer' }], - ['pressed', { type: 'tristate' }], - ['readonly', { type: 'boolean' }], - ['relevant', { - type: 'tokenlist', - whitelist: 'additions all removals text'.split(' ') - }], - ['required', { type: 'boolean' }], - ['roledescription', { type: 'string' }], - ['rowcount', { type: 'integer' }], - ['rowindex', { type: 'integer' }], - ['rowspan', { type: 'integer' }], - ['selected', { type: 'boolean_or_undefined' }], - ['setsize', { type: 'integer' }], - ['sort', { - type: 'token', - whitelist: 'ascending descending none other'.split(' ') - }], - ['valuemax', { type: 'number' }], - ['valuemin', { type: 'number' }], - ['valuenow', { type: 'number' }], - ['valuetext', { type: 'string' }] -]); - const a11y_distracting_elements = new Set([ 'blink', 'marquee' @@ -256,30 +177,27 @@ function get_namespace(parent: Element, element: Element, explicit_namespace: st return parent_element.namespace; } -function is_valid_aria_attribute_value(schema: any, value: any): boolean { +function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: string | boolean): boolean { switch (schema.type) { case 'boolean': - case 'boolean_or_undefined': return typeof value === 'boolean'; case 'string': - return typeof value === 'string'; case 'id': return typeof value === 'string'; case 'tristate': return typeof value === 'boolean' || value === 'mixed'; case 'integer': - return typeof value !== 'boolean' && isNaN(Number(value)) === false; case 'number': return typeof value !== 'boolean' && isNaN(Number(value)) === false; case 'token': // single token - return schema.whitelist + return (schema.values || []) .indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1; case 'idlist': // if list of ids, split each return typeof value === 'string' && value.split(' ').every((id) => typeof id === 'string'); case 'tokenlist': // if list of tokens, split each return typeof value === 'string' - && value.split(' ').every((token) => schema.whitelist.indexOf(token.toLowerCase()) > -1); + && value.split(' ').every((token) => (schema.values || []).indexOf(token.toLowerCase()) > -1); default: return false; } @@ -545,8 +463,8 @@ export default class Element extends Node { if (value === 'true') value = true; if (value === 'false') value = false; - if (value !== null && value !== undefined && a11y_attribute_types.has(type)) { - const schema = a11y_attribute_types.get(type); + if (value !== null && value !== undefined && aria.has(name as ARIAProperty)) { + const schema = aria.get(name as ARIAProperty); if (!is_valid_aria_attribute_value(schema, value)) { component.warn(attribute, compiler_warnings.a11y_incorrect_attribute_type(schema, name)); } diff --git a/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/input.svelte b/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/input.svelte deleted file mode 100644 index c52e60008f3..00000000000 --- a/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/input.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - -
-
-
-
\ No newline at end of file diff --git a/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/warnings.json b/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/warnings.json deleted file mode 100644 index 503d98f5d8e..00000000000 --- a/test/validator/samples/a11y-aria-proptypes-boolean-or-undefined/warnings.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "code": "a11y-incorrect-aria-attribute-type", - "end": { - "character": 63, - "column": 22, - "line": 5 - }, - "message": "A11y: The value of 'aria-hidden' must be exactly one of true, false, or undefined", - "pos": 46, - "start": { - "character": 46, - "column": 5, - "line": 5 - } - }, - { - "code": "a11y-incorrect-aria-attribute-type", - "end": { - "character": 87, - "column": 21, - "line": 6 - }, - "message": "A11y: The value of 'aria-hidden' must be exactly one of true, false, or undefined", - "pos": 71, - "start": { - "character": 71, - "column": 5, - "line": 6 - } - } -] \ No newline at end of file